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

Last change on this file since 4874 was 4874, checked in by jttt, 12 years ago

Use static class were appropriate

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