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