source: josm/trunk/src/org/openstreetmap/josm/io/session/SessionReader.java@ 18466

Last change on this file since 18466 was 18466, checked in by taylor.smock, 23 months ago

Fix #21813: Improve marker handling in sessions and #21923: Improve session workflow/Add "save session" (patch by Bjoeni)

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