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

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

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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