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

Last change on this file since 5505 was 5505, checked in by bastiK, 12 years ago

add session support for geoimage layers (see #4029)

  • Property svn:eol-style set to native
File size: 19.9 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;
5import static org.openstreetmap.josm.tools.Utils.equal;
6
7import java.io.BufferedInputStream;
8import java.io.File;
9import java.io.FileInputStream;
10import java.io.FileNotFoundException;
11import java.io.IOException;
12import java.io.InputStream;
13import java.lang.reflect.InvocationTargetException;
14import java.net.URI;
15import java.net.URISyntaxException;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.Enumeration;
19import java.util.HashMap;
20import java.util.LinkedHashMap;
21import java.util.List;
22import java.util.Map;
23import java.util.Map.Entry;
24import java.util.TreeMap;
25import java.util.zip.ZipEntry;
26import java.util.zip.ZipException;
27import java.util.zip.ZipFile;
28
29import javax.swing.JOptionPane;
30import javax.swing.SwingUtilities;
31import javax.xml.parsers.DocumentBuilder;
32import javax.xml.parsers.DocumentBuilderFactory;
33import javax.xml.parsers.ParserConfigurationException;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.gui.ExtendedDialog;
37import org.openstreetmap.josm.gui.layer.Layer;
38import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
39import org.openstreetmap.josm.gui.progress.ProgressMonitor;
40import org.openstreetmap.josm.io.IllegalDataException;
41import org.openstreetmap.josm.tools.MultiMap;
42import org.openstreetmap.josm.tools.Utils;
43import org.w3c.dom.Document;
44import org.w3c.dom.Element;
45import org.w3c.dom.Node;
46import org.w3c.dom.NodeList;
47import org.xml.sax.SAXException;
48
49/**
50 * Reads a .jos session file and loads the layers in the process.
51 *
52 */
53public class SessionReader {
54
55 private static Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters = new HashMap<String, Class<? extends SessionLayerImporter>>();
56 static {
57 registerSessionLayerImporter("osm-data", OsmDataSessionImporter.class);
58 registerSessionLayerImporter("imagery", ImagerySessionImporter.class);
59 registerSessionLayerImporter("tracks", GpxTracksSessionImporter.class);
60 registerSessionLayerImporter("geoimage", GeoImageSessionImporter.class);
61 }
62
63 public static void registerSessionLayerImporter(String layerType, Class<? extends SessionLayerImporter> importer) {
64 sessionLayerImporters.put(layerType, importer);
65 }
66
67 public static SessionLayerImporter getSessionLayerImporter(String layerType) {
68 Class<? extends SessionLayerImporter> importerClass = sessionLayerImporters.get(layerType);
69 if (importerClass == null)
70 return null;
71 SessionLayerImporter importer = null;
72 try {
73 importer = importerClass.newInstance();
74 } catch (InstantiationException e) {
75 throw new RuntimeException(e);
76 } catch (IllegalAccessException e) {
77 throw new RuntimeException(e);
78 }
79 return importer;
80 }
81
82 private File sessionFile;
83 private boolean zip; /* true, if session file is a .joz file; false if it is a .jos file */
84 private ZipFile zipFile;
85 private List<Layer> layers = new ArrayList<Layer>();
86 private List<Runnable> postLoadTasks = new ArrayList<Runnable>();
87
88 /**
89 * @return list of layers that are later added to the mapview
90 */
91 public List<Layer> getLayers() {
92 return layers;
93 }
94
95 /**
96 * @return actions executed in EDT after layers have been added (message dialog, etc.)
97 */
98 public List<Runnable> getPostLoadTasks() {
99 return postLoadTasks;
100 }
101
102 public class ImportSupport {
103
104 private String layerName;
105 private int layerIndex;
106 private List<LayerDependency> layerDependencies;
107
108 public ImportSupport(String layerName, int layerIndex, List<LayerDependency> layerDependencies) {
109 this.layerName = layerName;
110 this.layerIndex = layerIndex;
111 this.layerDependencies = layerDependencies;
112 }
113
114 /**
115 * Path of the file inside the zip archive.
116 * Used as alternative return value for getFile method.
117 */
118 private String inZipPath;
119
120 /**
121 * Add a task, e.g. a message dialog, that should
122 * be executed in EDT after all layers have been added.
123 */
124 public void addPostLayersTask(Runnable task) {
125 postLoadTasks.add(task);
126 }
127
128 /**
129 * Return an InputStream for a URI from a .jos/.joz file.
130 *
131 * The following forms are supported:
132 *
133 * - absolute file (both .jos and .joz):
134 * "file:///home/user/data.osm"
135 * "file:/home/user/data.osm"
136 * "file:///C:/files/data.osm"
137 * "file:/C:/file/data.osm"
138 * "/home/user/data.osm"
139 * "C:\files\data.osm" (not a URI, but recognized by File constructor on Windows systems)
140 * - standalone .jos files:
141 * - relative uri:
142 * "save/data.osm"
143 * "../project2/data.osm"
144 * - for .joz files:
145 * - file inside zip archive:
146 * "layers/01/data.osm"
147 * - relativ to the .joz file:
148 * "../save/data.osm" ("../" steps out of the archive)
149 *
150 * @throws IOException Thrown when no Stream can be opened for the given URI, e.g. when the linked file has been deleted.
151 */
152 public InputStream getInputStream(String uriStr) throws IOException {
153 File file = getFile(uriStr);
154 if (file != null) {
155 try {
156 return new BufferedInputStream(new FileInputStream(file));
157 } catch (FileNotFoundException e) {
158 throw new IOException(tr("File ''{0}'' does not exist.", file.getPath()));
159 }
160 } else if (inZipPath != null) {
161 ZipEntry entry = zipFile.getEntry(inZipPath);
162 if (entry != null) {
163 InputStream is = zipFile.getInputStream(entry);
164 return is;
165 }
166 }
167 throw new IOException(tr("Unable to locate file ''{0}''.", uriStr));
168 }
169
170 /**
171 * Return a File for a URI from a .jos/.joz file.
172 *
173 * Returns null if the URI points to a file inside the zip archive.
174 * In this case, inZipPath will be set to the corresponding path.
175 */
176 public File getFile(String uriStr) throws IOException {
177 inZipPath = null;
178 try {
179 URI uri = new URI(uriStr);
180 if ("file".equals(uri.getScheme()))
181 // absolute path
182 return new File(uri);
183 else if (uri.getScheme() == null) {
184 // Check if this is an absolute path without 'file:' scheme part.
185 // At this point, (as an exception) platform dependent path separator will be recognized.
186 // (This form is discouraged, only for users that like to copy and paste a path manually.)
187 File file = new File(uriStr);
188 if (file.isAbsolute())
189 return file;
190 else {
191 // for relative paths, only forward slashes are permitted
192 if (isZip()) {
193 if (uri.getPath().startsWith("../")) {
194 // relative to session file - "../" step out of the archive
195 String relPath = uri.getPath().substring(3);
196 return new File(sessionFile.toURI().resolve(relPath));
197 } else {
198 // file inside zip archive
199 inZipPath = uriStr;
200 return null;
201 }
202 } else
203 return new File(sessionFile.toURI().resolve(uri));
204 }
205 } else
206 throw new IOException(tr("Unsupported scheme ''{0}'' in URI ''{1}''.", uri.getScheme(), uriStr));
207 } catch (URISyntaxException e) {
208 throw new IOException(e);
209 }
210 }
211
212 /**
213 * Returns true if we are reading from a .joz file.
214 */
215 public boolean isZip() {
216 return zip;
217 }
218
219 /**
220 * Name of the layer that is currently imported.
221 */
222 public String getLayerName() {
223 return layerName;
224 }
225
226 /**
227 * Index of the layer that is currently imported.
228 */
229 public int getLayerIndex() {
230 return layerIndex;
231 }
232
233 /**
234 * Dependencies - maps the layer index to the importer of the given
235 * layer. All the dependent importers have loaded completely at this point.
236 */
237 public List<LayerDependency> getLayerDependencies() {
238 return layerDependencies;
239 }
240 }
241
242 public static class LayerDependency {
243 private Integer index;
244 private Layer layer;
245 private SessionLayerImporter importer;
246
247 public LayerDependency(Integer index, Layer layer, SessionLayerImporter importer) {
248 this.index = index;
249 this.layer = layer;
250 this.importer = importer;
251 }
252
253 public SessionLayerImporter getImporter() {
254 return importer;
255 }
256
257 public Integer getIndex() {
258 return index;
259 }
260
261 public Layer getLayer() {
262 return layer;
263 }
264 }
265
266 private void error(String msg) throws IllegalDataException {
267 throw new IllegalDataException(msg);
268 }
269
270 private void parseJos(Document doc, ProgressMonitor progressMonitor) throws IllegalDataException {
271 Element root = doc.getDocumentElement();
272 if (!equal(root.getTagName(), "josm-session")) {
273 error(tr("Unexpected root element ''{0}'' in session file", root.getTagName()));
274 }
275 String version = root.getAttribute("version");
276 if (!"0.1".equals(version)) {
277 error(tr("Version ''{0}'' of session file is not supported. Expected: 0.1", version));
278 }
279
280 NodeList layersNL = root.getElementsByTagName("layers");
281 if (layersNL.getLength() == 0) return;
282
283 Element layersEl = (Element) layersNL.item(0);
284
285 MultiMap<Integer, Integer> deps = new MultiMap<Integer, Integer>();
286 Map<Integer, Element> elems = new HashMap<Integer, Element>();
287
288 NodeList nodes = layersEl.getChildNodes();
289
290 for (int i=0; i<nodes.getLength(); ++i) {
291 Node node = nodes.item(i);
292 if (node.getNodeType() == Node.ELEMENT_NODE) {
293 Element e = (Element) node;
294 if (equal(e.getTagName(), "layer")) {
295
296 if (!e.hasAttribute("index")) {
297 error(tr("missing mandatory attribute ''index'' for element ''layer''"));
298 }
299 Integer idx = null;
300 try {
301 idx = Integer.parseInt(e.getAttribute("index"));
302 } catch (NumberFormatException ex) {}
303 if (idx == null) {
304 error(tr("unexpected format of attribute ''index'' for element ''layer''"));
305 }
306 if (elems.containsKey(idx)) {
307 error(tr("attribute ''index'' ({0}) for element ''layer'' must be unique", Integer.toString(idx)));
308 }
309 elems.put(idx, e);
310
311 deps.putVoid(idx);
312 String depStr = e.getAttribute("depends");
313 if (depStr != null) {
314 for (String sd : depStr.split(",")) {
315 Integer d = null;
316 try {
317 d = Integer.parseInt(sd);
318 } catch (NumberFormatException ex) {}
319 if (d != null) {
320 deps.put(idx, d);
321 }
322 }
323 }
324 }
325 }
326 }
327
328 List<Integer> sorted = Utils.topologicalSort(deps);
329 final Map<Integer, Layer> layersMap = new TreeMap<Integer, Layer>(Collections.reverseOrder());
330 final Map<Integer, SessionLayerImporter> importers = new HashMap<Integer, SessionLayerImporter>();
331 final Map<Integer, String> names = new HashMap<Integer, String>();
332
333 progressMonitor.setTicksCount(sorted.size());
334 LAYER: for (int idx: sorted) {
335 Element e = elems.get(idx);
336 if (e == null) {
337 error(tr("missing layer with index {0}", idx));
338 }
339 if (!e.hasAttribute("name")) {
340 error(tr("missing mandatory attribute ''name'' for element ''layer''"));
341 }
342 String name = e.getAttribute("name");
343 names.put(idx, name);
344 if (!e.hasAttribute("type")) {
345 error(tr("missing mandatory attribute ''type'' for element ''layer''"));
346 }
347 String type = e.getAttribute("type");
348 SessionLayerImporter imp = getSessionLayerImporter(type);
349 if (imp == null) {
350 CancelOrContinueDialog dialog = new CancelOrContinueDialog();
351 dialog.show(
352 tr("Unable to load layer"),
353 tr("Cannot load layer of type ''{0}'' because no suitable importer was found.", type),
354 JOptionPane.WARNING_MESSAGE,
355 progressMonitor
356 );
357 if (dialog.isCancel()) {
358 progressMonitor.cancel();
359 return;
360 } else {
361 continue;
362 }
363 } else {
364 importers.put(idx, imp);
365 List<LayerDependency> depsImp = new ArrayList<LayerDependency>();
366 for (int d : deps.get(idx)) {
367 SessionLayerImporter dImp = importers.get(d);
368 if (dImp == null) {
369 CancelOrContinueDialog dialog = new CancelOrContinueDialog();
370 dialog.show(
371 tr("Unable to load layer"),
372 tr("Cannot load layer {0} because it depends on layer {1} which has been skipped.", idx, d),
373 JOptionPane.WARNING_MESSAGE,
374 progressMonitor
375 );
376 if (dialog.isCancel()) {
377 progressMonitor.cancel();
378 return;
379 } else {
380 continue LAYER;
381 }
382 }
383 depsImp.add(new LayerDependency(d, layersMap.get(d), dImp));
384 }
385 ImportSupport support = new ImportSupport(name, idx, depsImp);
386 Layer layer = null;
387 Exception exception = null;
388 try {
389 layer = imp.load(e, support, progressMonitor.createSubTaskMonitor(1, false));
390 } catch (IllegalDataException ex) {
391 exception = ex;
392 } catch (IOException ex) {
393 exception = ex;
394 }
395 if (exception != null) {
396 exception.printStackTrace();
397 CancelOrContinueDialog dialog = new CancelOrContinueDialog();
398 dialog.show(
399 tr("Error loading layer"),
400 tr("<html>Could not load layer {0} ''{1}''.<br>Error is:<br>{2}</html>", idx, name, exception.getMessage()),
401 JOptionPane.ERROR_MESSAGE,
402 progressMonitor
403 );
404 if (dialog.isCancel()) {
405 progressMonitor.cancel();
406 return;
407 } else {
408 continue;
409 }
410 }
411
412 if (layer == null) throw new RuntimeException();
413 layersMap.put(idx, layer);
414 }
415 progressMonitor.worked(1);
416 }
417
418 layers = new ArrayList<Layer>();
419 for (Entry<Integer, Layer> e : layersMap.entrySet()) {
420 Layer l = e.getValue();
421 if (l == null) {
422 continue;
423 }
424 l.setName(names.get(e.getKey()));
425 layers.add(l);
426 }
427 }
428
429 /**
430 * Show Dialog when there is an error for one layer.
431 * Ask the user whether to cancel the complete session loading or just to skip this layer.
432 *
433 * This is expected to run in a worker thread (PleaseWaitRunnable), so invokeAndWait is
434 * needed to block the current thread and wait for the result of the modal dialog from EDT.
435 */
436 private static class CancelOrContinueDialog {
437
438 private boolean cancel;
439
440 public void show(final String title, final String message, final int icon, final ProgressMonitor progressMonitor) {
441 try {
442 SwingUtilities.invokeAndWait(new Runnable() {
443 @Override public void run() {
444 ExtendedDialog dlg = new ExtendedDialog(
445 Main.parent,
446 title,
447 new String[] { tr("Cancel"), tr("Skip layer and continue") }
448 );
449 dlg.setButtonIcons(new String[] {"cancel", "dialogs/next"});
450 dlg.setIcon(icon);
451 dlg.setContent(message);
452 dlg.showDialog();
453 cancel = dlg.getValue() != 2;
454 }
455 });
456 } catch (InvocationTargetException ex) {
457 throw new RuntimeException(ex);
458 } catch (InterruptedException ex) {
459 throw new RuntimeException(ex);
460 }
461 }
462
463 public boolean isCancel() {
464 return cancel;
465 }
466 }
467
468 public void loadSession(File sessionFile, boolean zip, ProgressMonitor progressMonitor) throws IllegalDataException, IOException {
469 if (progressMonitor == null) {
470 progressMonitor = NullProgressMonitor.INSTANCE;
471 }
472 this.sessionFile = sessionFile;
473 this.zip = zip;
474
475 InputStream josIS = null;
476
477 if (zip) {
478 try {
479 zipFile = new ZipFile(sessionFile);
480 ZipEntry josEntry = null;
481 Enumeration<? extends ZipEntry> entries = zipFile.entries();
482 while (entries.hasMoreElements()) {
483 ZipEntry entry = entries.nextElement();
484 if (entry.getName().toLowerCase().endsWith(".jos")) {
485 josEntry = entry;
486 break;
487 }
488 }
489 if (josEntry == null) {
490 error(tr("expected .jos file inside .joz archive"));
491 }
492 josIS = zipFile.getInputStream(josEntry);
493 } catch (ZipException ze) {
494 throw new IOException(ze);
495 }
496 } else {
497 try {
498 josIS = new FileInputStream(sessionFile);
499 } catch (FileNotFoundException ex) {
500 throw new IOException(ex);
501 }
502 }
503
504 try {
505 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
506 builderFactory.setValidating(false);
507 builderFactory.setNamespaceAware(true);
508 DocumentBuilder builder = builderFactory.newDocumentBuilder();
509 Document document = builder.parse(josIS);
510 parseJos(document, progressMonitor);
511 } catch (SAXException e) {
512 throw new IllegalDataException(e);
513 } catch (ParserConfigurationException e) {
514 throw new IOException(e);
515 }
516 }
517
518}
Note: See TracBrowser for help on using the repository browser.