001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io.session; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.GraphicsEnvironment; 007import java.io.BufferedInputStream; 008import java.io.File; 009import java.io.FileNotFoundException; 010import java.io.IOException; 011import java.io.InputStream; 012import java.lang.reflect.InvocationTargetException; 013import java.net.URI; 014import java.net.URISyntaxException; 015import java.nio.charset.StandardCharsets; 016import java.nio.file.Files; 017import java.util.ArrayList; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.Enumeration; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.TreeMap; 026import java.util.zip.ZipEntry; 027import java.util.zip.ZipException; 028import java.util.zip.ZipFile; 029 030import javax.swing.JOptionPane; 031import javax.swing.SwingUtilities; 032import javax.xml.parsers.ParserConfigurationException; 033 034import org.openstreetmap.josm.Main; 035import org.openstreetmap.josm.data.ViewportData; 036import org.openstreetmap.josm.data.coor.EastNorth; 037import org.openstreetmap.josm.data.coor.LatLon; 038import org.openstreetmap.josm.data.projection.Projection; 039import org.openstreetmap.josm.gui.ExtendedDialog; 040import org.openstreetmap.josm.gui.layer.Layer; 041import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 042import org.openstreetmap.josm.gui.progress.ProgressMonitor; 043import org.openstreetmap.josm.io.Compression; 044import org.openstreetmap.josm.io.IllegalDataException; 045import org.openstreetmap.josm.tools.CheckParameterUtil; 046import org.openstreetmap.josm.tools.JosmRuntimeException; 047import org.openstreetmap.josm.tools.Logging; 048import org.openstreetmap.josm.tools.MultiMap; 049import org.openstreetmap.josm.tools.Utils; 050import org.openstreetmap.josm.tools.XmlUtils; 051import org.w3c.dom.Document; 052import org.w3c.dom.Element; 053import org.w3c.dom.Node; 054import org.w3c.dom.NodeList; 055import org.xml.sax.SAXException; 056 057/** 058 * Reads a .jos session file and loads the layers in the process. 059 * @since 4668 060 */ 061public class SessionReader { 062 063 /** 064 * Data class for projection saved in the session file. 065 */ 066 public static class SessionProjectionChoiceData { 067 private final String projectionChoiceId; 068 private final Collection<String> subPreferences; 069 070 /** 071 * Construct a new SessionProjectionChoiceData. 072 * @param projectionChoiceId projection choice id 073 * @param subPreferences parameters for the projection choice 074 */ 075 public SessionProjectionChoiceData(String projectionChoiceId, Collection<String> subPreferences) { 076 this.projectionChoiceId = projectionChoiceId; 077 this.subPreferences = subPreferences; 078 } 079 080 /** 081 * Get the projection choice id. 082 * @return the projection choice id 083 */ 084 public String getProjectionChoiceId() { 085 return projectionChoiceId; 086 } 087 088 /** 089 * Get the parameters for the projection choice 090 * @return parameters for the projection choice 091 */ 092 public Collection<String> getSubPreferences() { 093 return subPreferences; 094 } 095 } 096 097 /** 098 * Data class for viewport saved in the session file. 099 */ 100 public static class SessionViewportData { 101 private final LatLon center; 102 private final double meterPerPixel; 103 104 /** 105 * Construct a new SessionViewportData. 106 * @param center the lat/lon coordinates of the screen center 107 * @param meterPerPixel scale in meters per pixel 108 */ 109 public SessionViewportData(LatLon center, double meterPerPixel) { 110 CheckParameterUtil.ensureParameterNotNull(center); 111 this.center = center; 112 this.meterPerPixel = meterPerPixel; 113 } 114 115 /** 116 * Get the lat/lon coordinates of the screen center. 117 * @return lat/lon coordinates of the screen center 118 */ 119 public LatLon getCenter() { 120 return center; 121 } 122 123 /** 124 * Get the scale in meters per pixel. 125 * @return scale in meters per pixel 126 */ 127 public double getScale() { 128 return meterPerPixel; 129 } 130 131 /** 132 * Convert this viewport data to a {@link ViewportData} object (with projected coordinates). 133 * @param proj the projection to convert from lat/lon to east/north 134 * @return the corresponding ViewportData object 135 */ 136 public ViewportData getEastNorthViewport(Projection proj) { 137 EastNorth centerEN = proj.latlon2eastNorth(center); 138 // Get a "typical" distance in east/north units that 139 // corresponds to a couple of pixels. Shouldn't be too 140 // large, to keep it within projection bounds and 141 // not too small to avoid rounding errors. 142 double dist = 0.01 * proj.getDefaultZoomInPPD(); 143 LatLon ll1 = proj.eastNorth2latlon(new EastNorth(centerEN.east() - dist, centerEN.north())); 144 LatLon ll2 = proj.eastNorth2latlon(new EastNorth(centerEN.east() + dist, centerEN.north())); 145 double meterPerEasting = ll1.greatCircleDistance(ll2) / dist / 2; 146 double scale = meterPerPixel / meterPerEasting; // unit: easting per pixel 147 return new ViewportData(centerEN, scale); 148 } 149 } 150 151 private static final Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters = new HashMap<>(); 152 153 private URI sessionFileURI; 154 private boolean zip; // true, if session file is a .joz file; false if it is a .jos file 155 private ZipFile zipFile; 156 private List<Layer> layers = new ArrayList<>(); 157 private int active = -1; 158 private final List<Runnable> postLoadTasks = new ArrayList<>(); 159 private SessionViewportData viewport; 160 private SessionProjectionChoiceData projectionChoice; 161 162 static { 163 registerSessionLayerImporter("osm-data", OsmDataSessionImporter.class); 164 registerSessionLayerImporter("imagery", ImagerySessionImporter.class); 165 registerSessionLayerImporter("tracks", GpxTracksSessionImporter.class); 166 registerSessionLayerImporter("geoimage", GeoImageSessionImporter.class); 167 registerSessionLayerImporter("markers", MarkerSessionImporter.class); 168 registerSessionLayerImporter("osm-notes", NoteSessionImporter.class); 169 } 170 171 /** 172 * Register a session layer importer. 173 * 174 * @param layerType layer type 175 * @param importer importer for this layer class 176 */ 177 public static void registerSessionLayerImporter(String layerType, Class<? extends SessionLayerImporter> importer) { 178 sessionLayerImporters.put(layerType, importer); 179 } 180 181 /** 182 * Returns the session layer importer for the given layer type. 183 * @param layerType layer type to import 184 * @return session layer importer for the given layer 185 */ 186 public static SessionLayerImporter getSessionLayerImporter(String layerType) { 187 Class<? extends SessionLayerImporter> importerClass = sessionLayerImporters.get(layerType); 188 if (importerClass == null) 189 return null; 190 SessionLayerImporter importer = null; 191 try { 192 importer = importerClass.getConstructor().newInstance(); 193 } catch (ReflectiveOperationException e) { 194 throw new JosmRuntimeException(e); 195 } 196 return importer; 197 } 198 199 /** 200 * @return list of layers that are later added to the mapview 201 */ 202 public List<Layer> getLayers() { 203 return layers; 204 } 205 206 /** 207 * @return active layer, or {@code null} if not set 208 * @since 6271 209 */ 210 public Layer getActive() { 211 // layers is in reverse order because of the way TreeMap is built 212 return (active >= 0 && active < layers.size()) ? layers.get(layers.size()-1-active) : null; 213 } 214 215 /** 216 * @return actions executed in EDT after layers have been added (message dialog, etc.) 217 */ 218 public List<Runnable> getPostLoadTasks() { 219 return postLoadTasks; 220 } 221 222 /** 223 * Return the viewport (map position and scale). 224 * @return the viewport; can be null when no viewport info is found in the file 225 */ 226 public SessionViewportData getViewport() { 227 return viewport; 228 } 229 230 /** 231 * Return the projection choice data. 232 * @return the projection; can be null when no projection info is found in the file 233 */ 234 public SessionProjectionChoiceData getProjectionChoice() { 235 return projectionChoice; 236 } 237 238 /** 239 * A class that provides some context for the individual {@link SessionLayerImporter} 240 * when doing the import. 241 */ 242 public class ImportSupport { 243 244 private final String layerName; 245 private final int layerIndex; 246 private final List<LayerDependency> layerDependencies; 247 248 /** 249 * Path of the file inside the zip archive. 250 * Used as alternative return value for getFile method. 251 */ 252 private String inZipPath; 253 254 /** 255 * Constructs a new {@code ImportSupport}. 256 * @param layerName layer name 257 * @param layerIndex layer index 258 * @param layerDependencies layer dependencies 259 */ 260 public ImportSupport(String layerName, int layerIndex, List<LayerDependency> layerDependencies) { 261 this.layerName = layerName; 262 this.layerIndex = layerIndex; 263 this.layerDependencies = layerDependencies; 264 } 265 266 /** 267 * Add a task, e.g. a message dialog, that should 268 * be executed in EDT after all layers have been added. 269 * @param task task to run in EDT 270 */ 271 public void addPostLayersTask(Runnable task) { 272 postLoadTasks.add(task); 273 } 274 275 /** 276 * Return an InputStream for a URI from a .jos/.joz file. 277 * 278 * The following forms are supported: 279 * 280 * - absolute file (both .jos and .joz): 281 * "file:///home/user/data.osm" 282 * "file:/home/user/data.osm" 283 * "file:///C:/files/data.osm" 284 * "file:/C:/file/data.osm" 285 * "/home/user/data.osm" 286 * "C:\files\data.osm" (not a URI, but recognized by File constructor on Windows systems) 287 * - standalone .jos files: 288 * - relative uri: 289 * "save/data.osm" 290 * "../project2/data.osm" 291 * - for .joz files: 292 * - file inside zip archive: 293 * "layers/01/data.osm" 294 * - relativ to the .joz file: 295 * "../save/data.osm" ("../" steps out of the archive) 296 * @param uriStr URI as string 297 * @return the InputStream 298 * 299 * @throws IOException Thrown when no Stream can be opened for the given URI, e.g. when the linked file has been deleted. 300 */ 301 public InputStream getInputStream(String uriStr) throws IOException { 302 File file = getFile(uriStr); 303 if (file != null) { 304 try { 305 return new BufferedInputStream(Compression.getUncompressedFileInputStream(file)); 306 } catch (FileNotFoundException e) { 307 throw new IOException(tr("File ''{0}'' does not exist.", file.getPath()), e); 308 } 309 } else if (inZipPath != null) { 310 ZipEntry entry = zipFile.getEntry(inZipPath); 311 if (entry != null) { 312 return zipFile.getInputStream(entry); 313 } 314 } 315 throw new IOException(tr("Unable to locate file ''{0}''.", uriStr)); 316 } 317 318 /** 319 * Return a File for a URI from a .jos/.joz file. 320 * 321 * Returns null if the URI points to a file inside the zip archive. 322 * In this case, inZipPath will be set to the corresponding path. 323 * @param uriStr the URI as string 324 * @return the resulting File 325 * @throws IOException if any I/O error occurs 326 */ 327 public File getFile(String uriStr) throws IOException { 328 inZipPath = null; 329 try { 330 URI uri = new URI(uriStr); 331 if ("file".equals(uri.getScheme())) 332 // absolute path 333 return new File(uri); 334 else if (uri.getScheme() == null) { 335 // Check if this is an absolute path without 'file:' scheme part. 336 // At this point, (as an exception) platform dependent path separator will be recognized. 337 // (This form is discouraged, only for users that like to copy and paste a path manually.) 338 File file = new File(uriStr); 339 if (file.isAbsolute()) 340 return file; 341 else { 342 // for relative paths, only forward slashes are permitted 343 if (isZip()) { 344 if (uri.getPath().startsWith("../")) { 345 // relative to session file - "../" step out of the archive 346 String relPath = uri.getPath().substring(3); 347 return new File(sessionFileURI.resolve(relPath)); 348 } else { 349 // file inside zip archive 350 inZipPath = uriStr; 351 return null; 352 } 353 } else 354 return new File(sessionFileURI.resolve(uri)); 355 } 356 } else 357 throw new IOException(tr("Unsupported scheme ''{0}'' in URI ''{1}''.", uri.getScheme(), uriStr)); 358 } catch (URISyntaxException | IllegalArgumentException e) { 359 throw new IOException(e); 360 } 361 } 362 363 /** 364 * Determines if we are reading from a .joz file. 365 * @return {@code true} if we are reading from a .joz file, {@code false} otherwise 366 */ 367 public boolean isZip() { 368 return zip; 369 } 370 371 /** 372 * Name of the layer that is currently imported. 373 * @return layer name 374 */ 375 public String getLayerName() { 376 return layerName; 377 } 378 379 /** 380 * Index of the layer that is currently imported. 381 * @return layer index 382 */ 383 public int getLayerIndex() { 384 return layerIndex; 385 } 386 387 /** 388 * Dependencies - maps the layer index to the importer of the given 389 * layer. All the dependent importers have loaded completely at this point. 390 * @return layer dependencies 391 */ 392 public List<LayerDependency> getLayerDependencies() { 393 return layerDependencies; 394 } 395 396 @Override 397 public String toString() { 398 return "ImportSupport [layerName=" + layerName + ", layerIndex=" + layerIndex + ", layerDependencies=" 399 + layerDependencies + ", inZipPath=" + inZipPath + ']'; 400 } 401 } 402 403 public static class LayerDependency { 404 private final Integer index; 405 private final Layer layer; 406 private final SessionLayerImporter importer; 407 408 public LayerDependency(Integer index, Layer layer, SessionLayerImporter importer) { 409 this.index = index; 410 this.layer = layer; 411 this.importer = importer; 412 } 413 414 public SessionLayerImporter getImporter() { 415 return importer; 416 } 417 418 public Integer getIndex() { 419 return index; 420 } 421 422 public Layer getLayer() { 423 return layer; 424 } 425 } 426 427 private static void error(String msg) throws IllegalDataException { 428 throw new IllegalDataException(msg); 429 } 430 431 private void parseJos(Document doc, ProgressMonitor progressMonitor) throws IllegalDataException { 432 Element root = doc.getDocumentElement(); 433 if (!"josm-session".equals(root.getTagName())) { 434 error(tr("Unexpected root element ''{0}'' in session file", root.getTagName())); 435 } 436 String version = root.getAttribute("version"); 437 if (!"0.1".equals(version)) { 438 error(tr("Version ''{0}'' of session file is not supported. Expected: 0.1", version)); 439 } 440 441 viewport = readViewportData(root); 442 projectionChoice = readProjectionChoiceData(root); 443 444 Element layersEl = getElementByTagName(root, "layers"); 445 if (layersEl == null) return; 446 447 String activeAtt = layersEl.getAttribute("active"); 448 try { 449 active = !activeAtt.isEmpty() ? (Integer.parseInt(activeAtt)-1) : -1; 450 } catch (NumberFormatException e) { 451 Logging.warn("Unsupported value for 'active' layer attribute. Ignoring it. Error was: "+e.getMessage()); 452 active = -1; 453 } 454 455 MultiMap<Integer, Integer> deps = new MultiMap<>(); 456 Map<Integer, Element> elems = new HashMap<>(); 457 458 NodeList nodes = layersEl.getChildNodes(); 459 460 for (int i = 0; i < nodes.getLength(); ++i) { 461 Node node = nodes.item(i); 462 if (node.getNodeType() == Node.ELEMENT_NODE) { 463 Element e = (Element) node; 464 if ("layer".equals(e.getTagName())) { 465 if (!e.hasAttribute("index")) { 466 error(tr("missing mandatory attribute ''index'' for element ''layer''")); 467 } 468 Integer idx = null; 469 try { 470 idx = Integer.valueOf(e.getAttribute("index")); 471 } catch (NumberFormatException ex) { 472 Logging.warn(ex); 473 } 474 if (idx == null) { 475 error(tr("unexpected format of attribute ''index'' for element ''layer''")); 476 } else if (elems.containsKey(idx)) { 477 error(tr("attribute ''index'' ({0}) for element ''layer'' must be unique", Integer.toString(idx))); 478 } 479 elems.put(idx, e); 480 481 deps.putVoid(idx); 482 String depStr = e.getAttribute("depends"); 483 if (!depStr.isEmpty()) { 484 for (String sd : depStr.split(",")) { 485 Integer d = null; 486 try { 487 d = Integer.valueOf(sd); 488 } catch (NumberFormatException ex) { 489 Logging.warn(ex); 490 } 491 if (d != null) { 492 deps.put(idx, d); 493 } 494 } 495 } 496 } 497 } 498 } 499 500 List<Integer> sorted = Utils.topologicalSort(deps); 501 final Map<Integer, Layer> layersMap = new TreeMap<>(Collections.reverseOrder()); 502 final Map<Integer, SessionLayerImporter> importers = new HashMap<>(); 503 final Map<Integer, String> names = new HashMap<>(); 504 505 progressMonitor.setTicksCount(sorted.size()); 506 LAYER: for (int idx: sorted) { 507 Element e = elems.get(idx); 508 if (e == null) { 509 error(tr("missing layer with index {0}", idx)); 510 return; 511 } else if (!e.hasAttribute("name")) { 512 error(tr("missing mandatory attribute ''name'' for element ''layer''")); 513 return; 514 } 515 String name = e.getAttribute("name"); 516 names.put(idx, name); 517 if (!e.hasAttribute("type")) { 518 error(tr("missing mandatory attribute ''type'' for element ''layer''")); 519 return; 520 } 521 String type = e.getAttribute("type"); 522 SessionLayerImporter imp = getSessionLayerImporter(type); 523 if (imp == null && !GraphicsEnvironment.isHeadless()) { 524 CancelOrContinueDialog dialog = new CancelOrContinueDialog(); 525 dialog.show( 526 tr("Unable to load layer"), 527 tr("Cannot load layer of type ''{0}'' because no suitable importer was found.", type), 528 JOptionPane.WARNING_MESSAGE, 529 progressMonitor 530 ); 531 if (dialog.isCancel()) { 532 progressMonitor.cancel(); 533 return; 534 } else { 535 continue; 536 } 537 } else if (imp != null) { 538 importers.put(idx, imp); 539 List<LayerDependency> depsImp = new ArrayList<>(); 540 for (int d : deps.get(idx)) { 541 SessionLayerImporter dImp = importers.get(d); 542 if (dImp == null) { 543 CancelOrContinueDialog dialog = new CancelOrContinueDialog(); 544 dialog.show( 545 tr("Unable to load layer"), 546 tr("Cannot load layer {0} because it depends on layer {1} which has been skipped.", idx, d), 547 JOptionPane.WARNING_MESSAGE, 548 progressMonitor 549 ); 550 if (dialog.isCancel()) { 551 progressMonitor.cancel(); 552 return; 553 } else { 554 continue LAYER; 555 } 556 } 557 depsImp.add(new LayerDependency(d, layersMap.get(d), dImp)); 558 } 559 ImportSupport support = new ImportSupport(name, idx, depsImp); 560 Layer layer = null; 561 Exception exception = null; 562 try { 563 layer = imp.load(e, support, progressMonitor.createSubTaskMonitor(1, false)); 564 if (layer == null) { 565 throw new IllegalStateException("Importer " + imp + " returned null for " + support); 566 } 567 } catch (IllegalDataException | IllegalStateException | IOException ex) { 568 exception = ex; 569 } 570 if (exception != null) { 571 Logging.error(exception); 572 if (!GraphicsEnvironment.isHeadless()) { 573 CancelOrContinueDialog dialog = new CancelOrContinueDialog(); 574 dialog.show( 575 tr("Error loading layer"), 576 tr("<html>Could not load layer {0} ''{1}''.<br>Error is:<br>{2}</html>", idx, 577 Utils.escapeReservedCharactersHTML(name), 578 Utils.escapeReservedCharactersHTML(exception.getMessage())), 579 JOptionPane.ERROR_MESSAGE, 580 progressMonitor 581 ); 582 if (dialog.isCancel()) { 583 progressMonitor.cancel(); 584 return; 585 } else { 586 continue; 587 } 588 } 589 } 590 591 layersMap.put(idx, layer); 592 } 593 progressMonitor.worked(1); 594 } 595 596 layers = new ArrayList<>(); 597 for (Entry<Integer, Layer> entry : layersMap.entrySet()) { 598 Layer layer = entry.getValue(); 599 if (layer == null) { 600 continue; 601 } 602 Element el = elems.get(entry.getKey()); 603 if (el.hasAttribute("visible")) { 604 layer.setVisible(Boolean.parseBoolean(el.getAttribute("visible"))); 605 } 606 if (el.hasAttribute("opacity")) { 607 try { 608 double opacity = Double.parseDouble(el.getAttribute("opacity")); 609 layer.setOpacity(opacity); 610 } catch (NumberFormatException ex) { 611 Logging.warn(ex); 612 } 613 } 614 layer.setName(names.get(entry.getKey())); 615 layers.add(layer); 616 } 617 } 618 619 private static SessionViewportData readViewportData(Element root) { 620 Element viewportEl = getElementByTagName(root, "viewport"); 621 if (viewportEl == null) return null; 622 LatLon center = null; 623 Element centerEl = getElementByTagName(viewportEl, "center"); 624 if (centerEl == null || !centerEl.hasAttribute("lat") || !centerEl.hasAttribute("lon")) return null; 625 try { 626 center = new LatLon(Double.parseDouble(centerEl.getAttribute("lat")), 627 Double.parseDouble(centerEl.getAttribute("lon"))); 628 } catch (NumberFormatException ex) { 629 Logging.warn(ex); 630 } 631 if (center == null) return null; 632 Element scaleEl = getElementByTagName(viewportEl, "scale"); 633 if (scaleEl == null || !scaleEl.hasAttribute("meter-per-pixel")) return null; 634 try { 635 double scale = Double.parseDouble(scaleEl.getAttribute("meter-per-pixel")); 636 return new SessionViewportData(center, scale); 637 } catch (NumberFormatException ex) { 638 Logging.warn(ex); 639 return null; 640 } 641 } 642 643 private static SessionProjectionChoiceData readProjectionChoiceData(Element root) { 644 Element projectionEl = getElementByTagName(root, "projection"); 645 if (projectionEl == null) return null; 646 Element projectionChoiceEl = getElementByTagName(projectionEl, "projection-choice"); 647 if (projectionChoiceEl == null) return null; 648 Element idEl = getElementByTagName(projectionChoiceEl, "id"); 649 if (idEl == null) return null; 650 String id = idEl.getTextContent(); 651 Element parametersEl = getElementByTagName(projectionChoiceEl, "parameters"); 652 if (parametersEl == null) return null; 653 Collection<String> parameters = new ArrayList<>(); 654 NodeList paramNl = parametersEl.getElementsByTagName("param"); 655 for (int i = 0; i < paramNl.getLength(); i++) { 656 Element paramEl = (Element) paramNl.item(i); 657 parameters.add(paramEl.getTextContent()); 658 } 659 return new SessionProjectionChoiceData(id, parameters); 660 } 661 662 /** 663 * Show Dialog when there is an error for one layer. 664 * Ask the user whether to cancel the complete session loading or just to skip this layer. 665 * 666 * This is expected to run in a worker thread (PleaseWaitRunnable), so invokeAndWait is 667 * needed to block the current thread and wait for the result of the modal dialog from EDT. 668 */ 669 private static class CancelOrContinueDialog { 670 671 private boolean cancel; 672 673 public void show(final String title, final String message, final int icon, final ProgressMonitor progressMonitor) { 674 try { 675 SwingUtilities.invokeAndWait(() -> { 676 ExtendedDialog dlg = new ExtendedDialog( 677 Main.parent, 678 title, 679 tr("Cancel"), tr("Skip layer and continue")) 680 .setButtonIcons("cancel", "dialogs/next") 681 .setIcon(icon) 682 .setContent(message); 683 cancel = dlg.showDialog().getValue() != 2; 684 }); 685 } catch (InvocationTargetException | InterruptedException ex) { 686 throw new JosmRuntimeException(ex); 687 } 688 } 689 690 public boolean isCancel() { 691 return cancel; 692 } 693 } 694 695 /** 696 * Loads session from the given file. 697 * @param sessionFile session file to load 698 * @param zip {@code true} if it's a zipped session (.joz) 699 * @param progressMonitor progress monitor 700 * @throws IllegalDataException if invalid data is detected 701 * @throws IOException if any I/O error occurs 702 */ 703 public void loadSession(File sessionFile, boolean zip, ProgressMonitor progressMonitor) throws IllegalDataException, IOException { 704 try (InputStream josIS = createInputStream(sessionFile, zip)) { 705 loadSession(josIS, sessionFile.toURI(), zip, progressMonitor != null ? progressMonitor : NullProgressMonitor.INSTANCE); 706 } 707 } 708 709 private InputStream createInputStream(File sessionFile, boolean zip) throws IOException, IllegalDataException { 710 if (zip) { 711 try { 712 zipFile = new ZipFile(sessionFile, StandardCharsets.UTF_8); 713 return getZipInputStream(zipFile); 714 } catch (ZipException ex) { 715 throw new IOException(ex); 716 } 717 } else { 718 return Files.newInputStream(sessionFile.toPath()); 719 } 720 } 721 722 private static InputStream getZipInputStream(ZipFile zipFile) throws IOException, IllegalDataException { 723 ZipEntry josEntry = null; 724 Enumeration<? extends ZipEntry> entries = zipFile.entries(); 725 while (entries.hasMoreElements()) { 726 ZipEntry entry = entries.nextElement(); 727 if (Utils.hasExtension(entry.getName(), "jos")) { 728 josEntry = entry; 729 break; 730 } 731 } 732 if (josEntry == null) { 733 error(tr("expected .jos file inside .joz archive")); 734 } 735 return zipFile.getInputStream(josEntry); 736 } 737 738 private void loadSession(InputStream josIS, URI sessionFileURI, boolean zip, ProgressMonitor progressMonitor) 739 throws IOException, IllegalDataException { 740 741 this.sessionFileURI = sessionFileURI; 742 this.zip = zip; 743 744 try { 745 parseJos(XmlUtils.parseSafeDOM(josIS), progressMonitor); 746 } catch (SAXException e) { 747 throw new IllegalDataException(e); 748 } catch (ParserConfigurationException e) { 749 throw new IOException(e); 750 } 751 } 752 753 private static Element getElementByTagName(Element root, String name) { 754 NodeList els = root.getElementsByTagName(name); 755 return els.getLength() > 0 ? (Element) els.item(0) : null; 756 } 757}