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

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

add session support for imagery layers

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