Index: trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java	(revision 4668)
+++ trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java	(revision 4668)
@@ -0,0 +1,136 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.session.SessionReader;
+
+public class SessionLoadAction extends JosmAction {
+    public SessionLoadAction() {
+        super(tr("Load Session"), "open", tr("Load a session from file."), null, false);
+        putValue("help", ht("/Action/SessionLoad"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String curDir = Main.pref.get("lastDirectory");
+        if (curDir.equals("")) {
+            curDir = ".";
+        }
+        JFileChooser fc = new JFileChooser(new File(curDir));
+        fc.setDialogTitle(tr("Open session"));
+        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        fc.setMultiSelectionEnabled(false);
+        fc.setAcceptAllFileFilterUsed(true);
+        FileFilter ff = new ExtensionFileFilter("jos,joz", "jos", tr("Session file (*.jos, *.joz)"));
+        fc.addChoosableFileFilter(ff);
+        int answer = fc.showOpenDialog(Main.parent);
+        if (answer != JFileChooser.APPROVE_OPTION)
+            return;
+
+        if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) {
+            Main.pref.put("lastDirectory", fc.getCurrentDirectory().getAbsolutePath());
+        }
+        File file = fc.getSelectedFile();
+        boolean zip = true;
+        if (file.getName().toLowerCase().endsWith(".jos")) {
+            zip = false;
+        }
+        Main.worker.submit(new Loader(file, zip));
+    }
+
+    public class Loader extends PleaseWaitRunnable {
+
+        private boolean canceled;
+        private File file;
+        private boolean zip;
+        private List<Layer> layers;
+        private List<Runnable> postLoadTasks;
+
+        public Loader(File file, boolean zip) {
+            super(tr("Loading session ''{0}''", file.getName()));
+            this.file = file;
+            this.zip = zip;
+        }
+
+        @Override
+        protected void cancel() {
+            Thread.currentThread().dumpStack();
+            canceled = true;
+        }
+
+        @Override
+        protected void finish() {
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    if (canceled) return;
+                    for (Layer l : layers) {
+                        if (canceled) return;
+                        Main.main.addLayer(l);
+                    }
+                    for (Runnable task : postLoadTasks) {
+                        if (canceled) return;
+                        if (task == null) continue;
+                        task.run();
+                    }
+                }
+            });
+        }
+
+        @Override
+        protected void realRun() {
+            try {
+                ProgressMonitor monitor = getProgressMonitor();
+                SessionReader reader = new SessionReader();
+                reader.loadSession(file, zip, monitor);
+                layers = reader.getLayers();
+                postLoadTasks = reader.getPostLoadTasks();
+            } catch (IllegalDataException e) {
+                e.printStackTrace();
+                HelpAwareOptionPane.showMessageDialogInEDT(
+                        Main.parent,
+                        tr("<html>Could not load session file ''{0}''.<br>Error is:<br>{1}</html>", file.getName(), e.getMessage()),
+                        tr("Data Error"),
+                        JOptionPane.ERROR_MESSAGE,
+                        null
+                );
+                cancel();
+            } catch (IOException e) {
+                e.printStackTrace();
+                HelpAwareOptionPane.showMessageDialogInEDT(
+                        Main.parent,
+                        tr("<html>Could not load session file ''{0}''.<br>Error is:<br>{1}</html>", file.getName(), e.getMessage()),
+                        tr("IO Error"),
+                        JOptionPane.ERROR_MESSAGE,
+                        null
+                );
+                cancel();
+            } catch (RuntimeException e) {
+                cancel();
+                throw e;
+            } catch (Throwable t) {
+                cancel();
+                throw new RuntimeException(t);
+            }
+        }
+    }
+}
+
Index: trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 4667)
+++ trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 4668)
@@ -70,4 +70,5 @@
 import org.openstreetmap.josm.actions.SaveAsAction;
 import org.openstreetmap.josm.actions.SelectAllAction;
+import org.openstreetmap.josm.actions.SessionLoadAction;
 import org.openstreetmap.josm.actions.ShowStatusReportAction;
 import org.openstreetmap.josm.actions.SimplifyWayAction;
@@ -98,4 +99,5 @@
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.tagging.TaggingPresetSearchAction;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -117,4 +119,5 @@
     public final JosmAction save = new SaveAction();
     public final JosmAction saveAs = new SaveAsAction();
+    public final JosmAction sessionLoad = new SessionLoadAction();
     public final JosmAction gpxExport = new GpxExportAction();
     public final DownloadAction download = new DownloadAction();
@@ -190,4 +193,5 @@
 
     public final JMenu fileMenu = addMenu(marktr("File"), KeyEvent.VK_F, 0, ht("/Menu/File"));
+    public final JMenu sessionMenu = new JMenu(tr("Session")); // submenu of the file menu
     public final JMenu editMenu = addMenu(marktr("Edit"), KeyEvent.VK_E, 1, ht("/Menu/Edit"));
     public final JMenu viewMenu = addMenu(marktr("View"), KeyEvent.VK_V, 2, ht("/Menu/View"));
@@ -206,4 +210,5 @@
     public JMenu audioMenu = null;
     public final JMenu helpMenu = addMenu(marktr("Help"), KeyEvent.VK_H, 7, ht("/Menu/Help"));
+
     public final int defaultMenuPos = 7;
 
@@ -357,4 +362,10 @@
         add(fileMenu, save);
         add(fileMenu, saveAs);
+        if (Main.pref.getBoolean("session")) {
+            sessionMenu.setToolTipText(tr("Save and load the current session (list of layers, etc.)"));
+            sessionMenu.setIcon(ImageProvider.get("session"));
+            add(sessionMenu, sessionLoad);
+            fileMenu.add(sessionMenu);
+        }
         add(fileMenu, gpxExport);
         fileMenu.addSeparator();
Index: trunk/src/org/openstreetmap/josm/io/OsmImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmImporter.java	(revision 4667)
+++ trunk/src/org/openstreetmap/josm/io/OsmImporter.java	(revision 4668)
@@ -22,4 +22,7 @@
 public class OsmImporter extends FileImporter {
 
+    private OsmDataLayer layer;
+    private Runnable postLayerTask;
+
     public OsmImporter() {
         super(new ExtensionFileFilter("osm,xml", "osm", tr("OSM Server Files") + " (*.osm *.xml)"));
@@ -30,5 +33,6 @@
     }
 
-    @Override public void importData(File file, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
+    @Override
+    public void importData(File file, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
         try {
             FileInputStream in = new FileInputStream(file);
@@ -41,23 +45,11 @@
 
     protected void importData(InputStream in, final File associatedFile) throws IllegalDataException {
-        final DataSet dataSet = OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE);
-        final OsmDataLayer layer = new OsmDataLayer(dataSet, associatedFile.getName(), associatedFile);
-        addDataLayer(dataSet, layer, associatedFile.getPath()); 
-    }
-        
-    protected void addDataLayer(final DataSet dataSet, final OsmDataLayer layer, final String filePath) { 
+        loadLayer(in, associatedFile, associatedFile.getName(), NullProgressMonitor.INSTANCE);
         // FIXME: remove UI stuff from IO subsystem
-        //
         Runnable uiStuff = new Runnable() {
             @Override
             public void run() {
-                if (dataSet.allPrimitives().isEmpty()) {
-                    JOptionPane.showMessageDialog(
-                            Main.parent,
-                            tr("No data found in file {0}.", filePath),
-                            tr("Open OSM file"),
-                            JOptionPane.INFORMATION_MESSAGE);
-                }
                 Main.main.addLayer(layer);
+                postLayerTask.run();
                 layer.onPostLoadFromFile();
             }
@@ -69,3 +61,40 @@
         }
     }
+
+    /**
+     * Load osm data layer from InputStream.
+     * associatedFile can be null if the stream does not come from a file.
+     */
+    public void loadLayer(InputStream in, final File associatedFile, final String layerName, ProgressMonitor progressMonitor) throws IllegalDataException {
+        final DataSet dataSet = OsmReader.parseDataSet(in, progressMonitor);
+        String name = associatedFile == null ? OsmDataLayer.createNewName() : associatedFile.getName();
+        layer = new OsmDataLayer(dataSet, layerName, associatedFile);
+        postLayerTask = new Runnable() {
+            @Override
+            public void run() {
+                if (dataSet.allPrimitives().isEmpty()) {
+                    String msg;
+                    if (associatedFile == null) {
+                        msg = tr("No data found for layer ''{0}''.", layerName);
+                    } else {
+                        msg = tr("No data found in file ''{0}''.", associatedFile.getPath());
+                    }
+                    JOptionPane.showMessageDialog(
+                            Main.parent,
+                            msg,
+                            tr("Open OSM file"),
+                            JOptionPane.INFORMATION_MESSAGE);
+                }
+                layer.onPostLoadFromFile();
+            }
+        };
+    }
+
+    public OsmDataLayer getLayer() {
+        return layer;
+    }
+
+    public Runnable getPostLayerTask() {
+        return postLayerTask;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java	(revision 4668)
+++ trunk/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java	(revision 4668)
@@ -0,0 +1,52 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Element;
+
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.OsmImporter;
+import org.openstreetmap.josm.io.session.SessionReader.ImportSupport;
+
+public class OsmDataSessionImporter implements SessionLayerImporter {
+
+    @Override
+    public Layer load(Element elem, ImportSupport support, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
+        String version = elem.getAttribute("version");
+        if (!"0.1".equals(version)) {
+            throw new IllegalDataException(tr("Version ''{0}'' of meta data for osm data layer is not supported. Expected: 0.1", version));
+        }
+        try {
+            XPathFactory xPathFactory = XPathFactory.newInstance();
+            XPath xpath = xPathFactory.newXPath();
+            XPathExpression fileExp = xpath.compile("file/text()");
+            String fileStr = (String) fileExp.evaluate(elem, XPathConstants.STRING);
+            if (fileStr == null || fileStr.equals("")) {
+                throw new IllegalDataException(tr("File name expected for layer no. {0}", support.getLayerIndex()));
+            }
+
+            OsmImporter importer = new OsmImporter();
+            InputStream in = support.getInputStream(fileStr);
+            importer.loadLayer(in, support.getFile(fileStr), support.getLayerName(), progressMonitor);
+
+            support.addPostLayersTask(importer.getPostLayerTask());
+            return importer.getLayer();
+
+        } catch (XPathExpressionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
+
Index: trunk/src/org/openstreetmap/josm/io/session/SessionLayerImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/SessionLayerImporter.java	(revision 4668)
+++ trunk/src/org/openstreetmap/josm/io/session/SessionLayerImporter.java	(revision 4668)
@@ -0,0 +1,19 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import java.io.IOException;
+
+import org.w3c.dom.Element;
+
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.session.SessionReader.ImportSupport;
+
+public interface SessionLayerImporter {
+    /**
+     * Load the layer from xml meta-data.
+     */
+    Layer load(Element elem, ImportSupport support, ProgressMonitor progressMonitor) throws IOException, IllegalDataException;
+}
+
Index: trunk/src/org/openstreetmap/josm/io/session/SessionReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/SessionReader.java	(revision 4668)
+++ trunk/src/org/openstreetmap/josm/io/session/SessionReader.java	(revision 4668)
@@ -0,0 +1,500 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.Utils.equal;
+
+import java.awt.Component;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Reads a .jos session file and loads the layers in the process.
+ *
+ */
+public class SessionReader {
+
+    private static Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters = new HashMap<String, Class<? extends SessionLayerImporter>>();
+    static {
+        registerSessionLayerImporter("osm-data", OsmDataSessionImporter.class);
+    }
+
+    public static void registerSessionLayerImporter(String layerType, Class<? extends SessionLayerImporter> importer) {
+        sessionLayerImporters.put(layerType, importer);
+    }
+
+    public static SessionLayerImporter getSessionLayerImporter(String layerType) {
+        Class<? extends SessionLayerImporter> importerClass = sessionLayerImporters.get(layerType);
+        if (importerClass == null)
+            return null;
+        SessionLayerImporter importer = null;
+        try {
+            importer = importerClass.newInstance();
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        return importer;
+    }
+
+    private File sessionFile;
+    private boolean zip; /* true, if session file is a .joz file; false if it is a .jos file */
+    private ZipFile zipFile;
+    private List<Layer> layers = new ArrayList<Layer>();
+    private List<Runnable> postLoadTasks = new ArrayList<Runnable>();
+
+    /**
+     * @return list of layers that are later added to the mapview
+     */
+    public List<Layer> getLayers() {
+        return layers;
+    }
+
+    /**
+     * @return actions executed in EDT after layers have been added (message dialog, etc.)
+     */
+    public List<Runnable> getPostLoadTasks() {
+        return postLoadTasks;
+    }
+
+    public class ImportSupport {
+
+        private String layerName;
+        private int layerIndex;
+        private LinkedHashMap<Integer,SessionLayerImporter> layerDependencies;
+
+        public ImportSupport(String layerName, int layerIndex, LinkedHashMap<Integer,SessionLayerImporter> layerDependencies) {
+            this.layerName = layerName;
+            this.layerIndex = layerIndex;
+            this.layerDependencies = layerDependencies;
+        }
+
+        /**
+         * Path of the file inside the zip archive.
+         * Used as alternative return value for getFile method.
+         */
+        private String inZipPath;
+
+        /**
+         * Add a task, e.g. a message dialog, that should
+         * be executed in EDT after all layers have been added.
+         */
+        public void addPostLayersTask(Runnable task) {
+            postLoadTasks.add(task);
+        }
+
+        /**
+         * Return an InputStream for a URI from a .jos/.joz file.
+         *
+         * The following forms are supported:
+         *
+         * - absolute file (both .jos and .joz):
+         *         "file:///home/user/data.osm"
+         *         "file:/home/user/data.osm"
+         *         "file:///C:/files/data.osm"
+         *         "file:/C:/file/data.osm"
+         *         "/home/user/data.osm"
+         *         "C:\files\data.osm"          (not a URI, but recognized by File constructor on Windows systems)
+         * - standalone .jos files:
+         *     - relative uri:
+         *         "save/data.osm"
+         *         "../project2/data.osm"
+         * - for .joz files:
+         *     - file inside zip archive:
+         *         "layers/01/data.osm"
+         *     - relativ to the .joz file:
+         *         "../save/data.osm"           ("../" steps out of the archive)
+         *
+         * @throws IOException Thrown when no Stream can be opened for the given URI, e.g. when the linked file has been deleted.
+         */
+        public InputStream getInputStream(String uriStr) throws IOException {
+            File file = getFile(uriStr);
+            try {
+                if (file != null) {
+                    return new BufferedInputStream(new FileInputStream(file));
+                } else if (inZipPath != null) {
+                    ZipEntry entry = zipFile.getEntry(inZipPath);
+                    if (entry != null) {
+                        InputStream is = zipFile.getInputStream(entry);
+                        return is;
+                    }
+                }
+            } catch (FileNotFoundException e) {
+                throw new IOException(tr("File ''{0}'' does not exist.", file.getPath()));
+            }
+            throw new IOException(tr("Unable to locate file  ''{0}''.", uriStr));
+        }
+
+        /**
+         * Return a File for a URI from a .jos file.
+         *
+         * Returns null if the URI points to a file inside the zip archive.
+         * In this case, inZipPath will be set to the corresponding path.
+         */
+        public File getFile(String uriStr) throws IOException {
+            inZipPath = null;
+            try {
+                URI uri = new URI(uriStr);
+                if ("file".equals(uri.getScheme())) {
+                    // absolute path
+                    return new File(uri);
+                } else if (uri.getScheme() == null) {
+                    // Check if this is an absolute path without 'file:' scheme part.
+                    // At this point, (as an exception) platform dependent path separator will be recognized.
+                    // (This form is discouraged, only for users that like to copy and paste a path manually.)
+                    File file = new File(uriStr);
+                    if (file.isAbsolute()) {
+                        return file;
+                    } else {
+                        // for relative paths, only forward slashes are permitted
+                        if (isZip()) {
+                            if (uri.getPath().startsWith("../")) {
+                                // relative to session file - "../" step out of the archive
+                                String relPath = uri.getPath().substring(3);
+                                return new File(sessionFile.toURI().resolve(relPath));
+                            } else {
+                                // file inside zip archive
+                                inZipPath = uriStr;
+                                return null;
+                            }
+                        } else {
+                            return new File(sessionFile.toURI().resolve(uri));
+                        }
+                    }
+                } else {
+                    throw new IOException(tr("Unsupported scheme ''{0}'' in URI ''{1}''.", uri.getScheme(), uriStr));
+                }
+            } catch (URISyntaxException e) {
+                throw new IOException(e);
+            }
+        }
+
+        /**
+         * Returns true if we are reading from a .joz file.
+         */
+        public boolean isZip() {
+            return zip;
+        }
+
+        /**
+         * Name of the layer that is currently imported.
+         */
+        public String getLayerName() {
+            return layerName;
+        }
+
+        /**
+         * Index of the layer that is currently imported.
+         */
+        public int getLayerIndex() {
+            return layerIndex;
+        }
+
+        /**
+         * Dependencies - maps the layer index to the importer of the given
+         * layer. All the dependent importers have loaded completely at this point.
+         */
+        public LinkedHashMap<Integer,SessionLayerImporter> getLayerDependencies() {
+            return layerDependencies;
+        }
+    }
+
+    private void error(String msg) throws IllegalDataException {
+        throw new IllegalDataException(msg);
+    }
+
+    private void parseJos(Document doc, ProgressMonitor progressMonitor) throws IllegalDataException {
+        Element root = doc.getDocumentElement();
+        if (!equal(root.getTagName(), "josm-session")) {
+            error(tr("Unexpected root element ''{0}'' in session file", root.getTagName()));
+        }
+        String version = root.getAttribute("version");
+        if (!"0.1".equals(version)) {
+            error(tr("Version ''{0}'' of session file is not supported. Expected: 0.1", version));
+        }
+
+        NodeList layersNL = root.getElementsByTagName("layers");
+        if (layersNL.getLength() == 0) return;
+
+        Element layersEl = (Element) layersNL.item(0);
+
+        MultiMap<Integer, Integer> deps = new MultiMap<Integer, Integer>();
+        Map<Integer, Element> elems = new HashMap<Integer, Element>();
+
+        NodeList nodes = layersEl.getChildNodes();
+
+        for (int i=0; i<nodes.getLength(); ++i) {
+            Node node = nodes.item(i);
+            if (node.getNodeType() == Node.ELEMENT_NODE) {
+                Element e = (Element) node;
+                if (equal(e.getTagName(), "layer")) {
+
+                    if (!e.hasAttribute("index")) {
+                        error(tr("missing mandatory attribute ''index'' for element ''layer''"));
+                    }
+                    Integer idx = null;
+                    try {
+                        idx = Integer.parseInt(e.getAttribute("index"));
+                    } catch (NumberFormatException ex) {}
+                    if (idx == null) {
+                        error(tr("unexpected format of attribute ''index'' for element ''layer''"));
+                    }
+                    if (elems.containsKey(idx)) {
+                        error(tr("attribute ''index'' ({0}) for element ''layer'' must be unique", Integer.toString(idx)));
+                    }
+                    elems.put(idx, e);
+
+                    deps.putVoid(idx);
+                    String depStr = e.getAttribute("depends");
+                    if (depStr != null) {
+                        for (String sd : depStr.split(",")) {
+                            Integer d = null;
+                            try {
+                                d = Integer.parseInt(sd);
+                            } catch (NumberFormatException ex) {}
+                            if (d != null) {
+                                deps.put(idx, d);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        List<Integer> sorted = Utils.topologicalSort(deps);
+        final Map<Integer, Layer> layersMap = new TreeMap<Integer, Layer>(Collections.reverseOrder());
+        final Map<Integer, SessionLayerImporter> importers = new HashMap<Integer, SessionLayerImporter>();
+        final Map<Integer, String> names = new HashMap<Integer, String>();
+
+        progressMonitor.setTicksCount(sorted.size());
+        LAYER: for (int idx: sorted) {
+            Element e = elems.get(idx);
+            if (e == null) {
+                error(tr("missing layer with index {0}", idx));
+            }
+            if (!e.hasAttribute("name")) {
+                error(tr("missing mandatory attribute ''name'' for element ''layer''"));
+            }
+            String name = e.getAttribute("name");
+            names.put(idx, name);
+            if (!e.hasAttribute("type")) {
+                error(tr("missing mandatory attribute ''type'' for element ''layer''"));
+            }
+            String type = e.getAttribute("type");
+            SessionLayerImporter imp = getSessionLayerImporter(type);
+            if (imp == null) {
+                CancelOrContinueDialog dialog = new CancelOrContinueDialog();
+                dialog.show(
+                    tr("Unable to load layer"),
+                    tr("Cannot load layer of type ''{0}'' because no suitable importer was found.", type),
+                    JOptionPane.WARNING_MESSAGE,
+                    progressMonitor
+                );
+                if (dialog.isCancel()) {
+                    progressMonitor.cancel();
+                    return;
+                } else {
+                    continue;
+                }
+            } else {
+                importers.put(idx, imp);
+                LinkedHashMap<Integer,SessionLayerImporter> depsImp = new LinkedHashMap<Integer,SessionLayerImporter>();
+                for (int d : deps.get(idx)) {
+                    SessionLayerImporter dImp = importers.get(d);
+                    if (dImp == null) {
+                        CancelOrContinueDialog dialog = new CancelOrContinueDialog();
+                        dialog.show(
+                            tr("Unable to load layer"),
+                            tr("Cannot load layer {0} because it depends no layer {1} which has been skipped.", idx, d),
+                            JOptionPane.WARNING_MESSAGE,
+                            progressMonitor
+                        );
+                        if (dialog.isCancel()) {
+                            progressMonitor.cancel();
+                            return;
+                        } else {
+                            continue LAYER;
+                        }
+                    }
+                    depsImp.put(d, dImp);
+                }
+                ImportSupport support = new ImportSupport(name, idx, depsImp);
+                Layer layer = null;
+                Exception exception = null;
+                try {
+                    layer = imp.load(e, support, progressMonitor.createSubTaskMonitor(1, false));
+                } catch (IllegalDataException ex) {
+                    exception = ex;
+                } catch (IOException ex) {
+                    exception = ex;
+                }
+                if (exception != null) {
+                    exception.printStackTrace();
+                    CancelOrContinueDialog dialog = new CancelOrContinueDialog();
+                    dialog.show(
+                        tr("Error loading layer"),
+                        tr("<html>Could not load layer {0} ''{1}''.<br>Error is:<br>{2}</html>", idx, name, exception.getMessage()),
+                        JOptionPane.ERROR_MESSAGE,
+                        progressMonitor
+                    );
+                    if (dialog.isCancel()) {
+                        progressMonitor.cancel();
+                        return;
+                    } else {
+                        continue;
+                    }
+                }
+
+                if (layer == null) throw new RuntimeException();
+                layersMap.put(idx, layer);
+            }
+            progressMonitor.worked(1);
+        }
+
+        layers = new ArrayList<Layer>();
+        for (Entry<Integer, Layer> e : layersMap.entrySet()) {
+            Layer l = e.getValue();
+            if (l == null) continue;
+            l.setName(names.get(e.getKey()));
+            layers.add(l);
+        }
+    }
+
+    /**
+     * Show Dialog when there is an error for one layer.
+     * Ask the user whether to cancel the complete session loading or just to skip this layer.
+     *
+     * This is expected to run in a worker thread (PleaseWaitRunnable), so invokeAndWait is
+     * needed to block the current thread and wait for the result of the modal dialog from EDT.
+     */
+    private class CancelOrContinueDialog {
+
+        private boolean cancel;
+
+        public void show(final String title, final String message, final int icon, final ProgressMonitor progressMonitor) {
+            try {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                    @Override public void run() {
+                        Component parent;
+                        if (progressMonitor instanceof PleaseWaitProgressMonitor) {
+                            parent = ((PleaseWaitProgressMonitor) progressMonitor).getDialog();
+                        } else {
+                            parent = Main.parent;
+                        }
+                        ExtendedDialog dlg = new ExtendedDialog(
+                                parent,
+                                title,
+                                new String[] { tr("Cancel"), tr("Skip layer and continue") }
+                        );
+                        dlg.setButtonIcons(new String[] {"cancel", "dialogs/next"});
+                        dlg.setIcon(icon);
+                        dlg.setContent(message);
+                        dlg.showDialog();
+                        cancel = dlg.getValue() != 2;
+                    }
+                });
+            } catch (InvocationTargetException ex) {
+                throw new RuntimeException(ex);
+            } catch (InterruptedException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        public boolean isCancel() {
+            return cancel;
+        }
+    }
+
+    public void loadSession(File sessionFile, boolean zip, ProgressMonitor progressMonitor) throws IllegalDataException, IOException {
+        if (progressMonitor == null) {
+            progressMonitor = NullProgressMonitor.INSTANCE;
+        }
+        this.sessionFile = sessionFile;
+        this.zip = zip;
+
+        InputStream josIS = null;
+
+        if (zip) {
+            try {
+                zipFile = new ZipFile(sessionFile);
+                ZipEntry josEntry = null;
+                Enumeration<? extends ZipEntry> entries = zipFile.entries();
+                while (entries.hasMoreElements()) {
+                    ZipEntry entry = entries.nextElement();
+                    if (entry.getName().toLowerCase().endsWith(".jos")) {
+                        josEntry = entry;
+                        break;
+                    }
+                }
+                if (josEntry == null) {
+                    error(tr("expected .jos file inside .joz archive"));
+                }
+                josIS = zipFile.getInputStream(josEntry);
+            } catch (ZipException ze) {
+                throw new IOException(ze);
+            }
+        } else {
+            try {
+                josIS = new FileInputStream(sessionFile);
+            } catch (FileNotFoundException ex) {
+                throw new IOException(ex);
+            }
+        }
+
+        try {
+            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+            builderFactory.setValidating(false);
+            builderFactory.setNamespaceAware(true);
+            DocumentBuilder builder = builderFactory.newDocumentBuilder();
+            Document document = builder.parse(josIS);
+            parseJos(document, progressMonitor);
+        } catch (SAXException e) {
+            throw new IllegalDataException(e);
+        } catch (ParserConfigurationException e) {
+            throw new IOException(e);
+        }
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/tools/MultiMap.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/MultiMap.java	(revision 4667)
+++ trunk/src/org/openstreetmap/josm/tools/MultiMap.java	(revision 4668)
@@ -2,7 +2,9 @@
 package org.openstreetmap.josm.tools;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -13,5 +15,5 @@
  *
  * Corresponds to Google guava LinkedHashMultimap and Apache Collections MultiValueMap
- * but it is an independent (simplistic) implementation.
+ * but it is an independent (simple) implementation.
  *
  */
@@ -116,3 +118,31 @@
         return map.values();
     }
+
+    /**
+     * Removes a cerain key=value mapping
+     *
+     * @return true, if something was removed
+     */
+    public boolean remove(A key, B value) {
+        Set<B> values = get(key);
+        if (values != null) {
+            return values.remove(value);
+        }
+        return false;
+    }
+
+    /**
+     * Removes all mappings for a certain key
+     */
+    public LinkedHashSet<B> remove(A key) {
+        return map.remove(key);
+    }
+
+    public String toString() {
+        List<String> entries = new ArrayList<String>(map.size());
+        for (A key : map.keySet()) {
+            entries.add(key + "->{" + Utils.join(",", map.get(key)) + "}");
+        }
+        return "(" + Utils.join(",", entries) + ")";
+    }
 }
Index: trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 4667)
+++ trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 4668)
@@ -19,5 +19,7 @@
 import java.security.NoSuchAlgorithmException;
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 
 /**
@@ -62,5 +64,5 @@
 		return new FilteredCollection<T>(collection, predicate);
 	}
-    
+
     /**
      * Filter a collection by (sub)class.
@@ -233,5 +235,5 @@
     /**
      * <p>Utility method for closing an input stream.</p>
-     * 
+     *
      * @param is the input stream. May be null.
      */
@@ -247,5 +249,5 @@
     /**
      * <p>Utility method for closing an output stream.</p>
-     * 
+     *
      * @param os the output stream. May be null.
      */
@@ -261,5 +263,5 @@
     /**
      * <p>Utility method for closing a reader.</p>
-     * 
+     *
      * @param reader the reader. May be null.
      */
@@ -355,3 +357,42 @@
         return new String(hexChars);
     }
+
+    /**
+     * Topological sort.
+     *
+     * @param dependencies contains mappings (key -> value). In the final list of sorted objects, the key will come
+     * after the value. (In other words, the key depends on the value(s).)
+     * There must not be cyclic dependencies.
+     * @return the list of sorted objects
+     */
+    public static <T> List<T> topologicalSort(final MultiMap<T,T> dependencies) {
+        MultiMap<T,T> deps = new MultiMap<T,T>();
+        for (T key : dependencies.keySet()) {
+            deps.putVoid(key);
+            for (T val : dependencies.get(key)) {
+                deps.putVoid(val);
+                deps.put(key, val);
+            }
+        }
+
+        int size = deps.size();
+        List<T> sorted = new ArrayList<T>();
+        for (int i=0; i<size; ++i) {
+            T parentless = null;
+            for (T key : deps.keySet()) {
+                if (deps.get(key).size() == 0) {
+                    parentless = key;
+                    break;
+                }
+            }
+            if (parentless == null) throw new RuntimeException();
+            sorted.add(parentless);
+            deps.remove(parentless);
+            for (T key : deps.keySet()) {
+                deps.remove(key, parentless);
+            }
+        }
+        if (sorted.size() != size) throw new RuntimeException();
+        return sorted;
+    }
 }
