Index: src/org/openstreetmap/josm/actions/SessionLoadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SessionLoadAction.java	(revision 18392)
+++ src/org/openstreetmap/josm/actions/SessionLoadAction.java	(working copy)
@@ -205,6 +205,7 @@
                     postLoadTasks = reader.getPostLoadTasks();
                     viewport = reader.getViewport();
                     projectionChoice = reader.getProjectionChoice();
+                    SessionSaveAction.setCurrentSession(file, zip, reader.getLayers());
                 } finally {
                     if (tempFile) {
                         Utils.deleteFile(file);
Index: src/org/openstreetmap/josm/actions/SessionSaveAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SessionSaveAction.java	(nonexistent)
+++ src/org/openstreetmap/josm/actions/SessionSaveAction.java	(working copy)
@@ -0,0 +1,519 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.data.PreferencesUtils;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapFrameListener;
+import org.openstreetmap.josm.gui.Notification;
+import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
+import org.openstreetmap.josm.gui.util.WindowGeometry;
+import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
+import org.openstreetmap.josm.io.session.SessionLayerExporter;
+import org.openstreetmap.josm.io.session.SessionWriter;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.UserCancelException;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Saves a JOSM session
+ * @since xxx
+ */
+public class SessionSaveAction extends DiskAccessAction implements MapFrameListener, LayerChangeListener {
+
+    private transient List<Layer> layers;
+    private transient Map<Layer, SessionLayerExporter> exporters;
+    private transient MultiMap<Layer, Layer> dependencies;
+
+    private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
+    private static final String TOOLTIP_DEFAULT = tr("Save the current session.");
+
+    protected FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
+    protected FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
+
+    private File removeFileOnSuccess;
+
+    private static String tooltip = TOOLTIP_DEFAULT;
+    protected static File sessionFile;
+    protected static boolean isZipSessionFile;
+    protected static List<WeakReference<Layer>> layersInSessionFile;
+
+    private static final SessionSaveAction instance = new SessionSaveAction();
+
+    /**
+     * Returns the instance
+     * @return the instance
+     */
+    public static final SessionSaveAction getInstance() {
+        return instance;
+    }
+
+    /**
+     * Constructs a new {@code SessionSaveAction}.
+     */
+    public SessionSaveAction() {
+        this(true, false);
+        updateEnabledState();
+    }
+
+    /**
+     * Constructs a new {@code SessionSaveAction}.
+     * @param toolbar Register this action for the toolbar preferences?
+     * @param installAdapters False, if you don't want to install layer changed and selection changed adapters
+     */
+    protected SessionSaveAction(boolean toolbar, boolean installAdapters) {
+        this(tr("Save Session"), "session", TOOLTIP_DEFAULT,
+                Shortcut.registerShortcut("system:savesession", tr("File: {0}", tr("Save Session...")), KeyEvent.VK_S, Shortcut.ALT_CTRL),
+                toolbar, "save-session", installAdapters);
+        setHelpId(ht("/Action/SessionSaveAs"));
+    }
+
+    protected SessionSaveAction(String name, String iconName, String tooltip,
+            Shortcut shortcut, boolean register, String toolbarId, boolean installAdapters) {
+
+        super(name, iconName, tooltip, shortcut, register, toolbarId, installAdapters);
+        MainApplication.addMapFrameListener(this);
+        MainApplication.getLayerManager().addLayerChangeListener(this);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            saveSession(false, false);
+        } catch (UserCancelException ignore) {
+            Logging.trace(ignore);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        MainApplication.removeMapFrameListener(this);
+        super.destroy();
+    }
+
+    /**
+     * Attempts to save the session.
+     * @param saveAs true shows the dialog
+     * @param forceSaveAll saves all layers
+     * @return if the session and all layers were successfully saved
+     * @throws UserCancelException when the user has cancelled the save process
+     */
+    public boolean saveSession(boolean saveAs, boolean forceSaveAll) throws UserCancelException {
+        if (!isEnabled()) {
+            return false;
+        }
+
+        removeFileOnSuccess = null;
+
+        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
+        if (saveAs) {
+            dlg.showDialog();
+            if (dlg.getValue() != 1) {
+                throw new UserCancelException();
+            }
+        }
+
+        // TODO: resolve dependencies for layers excluded by the user
+        List<Layer> layersOut = layers.stream()
+                .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())
+                .collect(Collectors.toList());
+
+        boolean zipRequired = layersOut.stream().map(l -> exporters.get(l))
+                .anyMatch(ex -> ex != null && ex.requiresZip());
+
+        saveAs = !doGetFile(saveAs, zipRequired);
+
+        String fn = sessionFile.getName();
+
+        if (!saveAs && layersInSessionFile != null) {
+            List<String> missingLayers = layersInSessionFile.stream()
+                    .map(WeakReference::get)
+                    .filter(Objects::nonNull)
+                    .filter(l -> !layersOut.contains(l))
+                    .map(Layer::getName)
+                    .collect(Collectors.toList());
+
+            if (!missingLayers.isEmpty() &&
+                    !ConditionalOptionPaneUtil.showConfirmationDialog(
+                            "savesession_layerremoved",
+                            null,
+                            new JLabel("<html>"
+                                    + trn("The following layer has been removed since the session was last saved:",
+                                          "The following layers have been removed since the session was last saved:", missingLayers.size())
+                                    + "<ul><li>"
+                                    + String.join("<li>", missingLayers)
+                                    + "</ul><br>"
+                                    + tr("You are about to overwrite the session file \"{0}\". Would you like to proceed?", fn)),
+                            tr("Layers removed"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE,
+                            JOptionPane.OK_OPTION)) {
+                throw new UserCancelException();
+            }
+        }
+        setCurrentLayers(layersOut);
+
+
+        if (fn.indexOf('.') == -1) {
+            sessionFile = new File(sessionFile.getPath() + (isZipSessionFile ? ".joz" : ".jos"));
+            if (!SaveActionBase.confirmOverwrite(sessionFile)) {
+                throw new UserCancelException();
+            }
+        }
+
+        Stream<Layer> layersToSaveStream = layersOut.stream()
+                .filter(layer -> layer.isSavable()
+                        && layer instanceof AbstractModifiableLayer
+                        && ((AbstractModifiableLayer) layer).requiresSaveToFile()
+                        && exporters.get(layer) != null
+                        && !exporters.get(layer).requiresZip());
+
+        boolean success = true;
+        if (forceSaveAll || SAVE_LOCAL_FILES_PROPERTY.get()) {
+            // individual files must be saved before the session file as the location may change
+            if (layersToSaveStream
+                .map(layer -> SaveAction.getInstance().doSave(layer, true))
+                .collect(Collectors.toList()) // force evaluation of all elements
+                .contains(false)) {
+
+                new Notification(tr("Not all local files referenced by the session file could be saved."
+                        + "<br>Make sure you save them before closing JOSM."))
+                    .setIcon(JOptionPane.WARNING_MESSAGE)
+                    .setDuration(Notification.TIME_LONG)
+                    .show();
+                success = false;
+            }
+        } else if (layersToSaveStream.anyMatch(l -> true)) {
+            new Notification(tr("Not all local files referenced by the session file are saved yet."
+                    + "<br>Make sure you save them before closing JOSM."))
+                .setIcon(JOptionPane.INFORMATION_MESSAGE)
+                .setDuration(Notification.TIME_LONG)
+                .show();
+        }
+
+        int active = -1;
+        Layer activeLayer = getLayerManager().getActiveLayer();
+        if (activeLayer != null) {
+            active = layersOut.indexOf(activeLayer);
+        }
+
+        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, isZipSessionFile);
+        try {
+            Notification savingNotification = showSavingNotification(sessionFile.getName());
+            sw.write(sessionFile);
+            SaveActionBase.addToFileOpenHistory(sessionFile);
+            if (removeFileOnSuccess != null) {
+                PreferencesUtils.removeFromList(Config.getPref(), "file-open.history", removeFileOnSuccess.getCanonicalPath());
+                removeFileOnSuccess.delete();
+                removeFileOnSuccess = null;
+            }
+            showSavedNotification(savingNotification, sessionFile.getName());
+        } catch (IOException ex) {
+            Logging.error(ex);
+            HelpAwareOptionPane.showMessageDialogInEDT(
+                    MainApplication.getMainFrame(),
+                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>",
+                            sessionFile.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())),
+                    tr("IO Error"),
+                    JOptionPane.ERROR_MESSAGE,
+                    null
+            );
+            success = false;
+        }
+        return success;
+    }
+
+    /**
+     * Sets the current session file. Asks the user if necessary
+     * @param saveAs alwas ask the user
+     * @param zipRequired zip
+     * @return if the user was asked
+     * @throws UserCancelException when the user has cancelled the save process
+     */
+    protected boolean doGetFile(boolean saveAs, boolean zipRequired) throws UserCancelException {
+        if (!saveAs && sessionFile != null) {
+
+            if (isZipSessionFile || !zipRequired)
+                return true;
+
+            Logging.info("Converting *.jos to *.joz because a new layer has been added that requires zip format");
+            String oldPath = sessionFile.getAbsolutePath();
+            int i = oldPath.lastIndexOf('.');
+            File jozFile = new File(i < 0 ? oldPath : oldPath.substring(0, i) + ".joz");
+            if (!jozFile.exists()) {
+                removeFileOnSuccess = sessionFile;
+                setCurrentSession(jozFile, true);
+                return true;
+            }
+            Logging.warn("Asking user to choose a new location for the *.joz file because it already exists");
+        }
+
+        doGetFileChooser(zipRequired);
+        return false;
+    }
+
+    protected void doGetFileChooser(boolean zipRequired) throws UserCancelException {
+        AbstractFileChooser fc;
+
+        if (zipRequired) {
+            fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
+        } else {
+            fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,
+                    JFileChooser.FILES_ONLY, "lastDirectory");
+        }
+
+        if (fc == null) {
+            throw new UserCancelException();
+        }
+
+        File f = fc.getSelectedFile();
+        FileFilter ff = fc.getFileFilter();
+        boolean zip;
+
+        if (zipRequired || joz.equals(ff)) {
+            zip = true;
+        } else if (jos.equals(ff)) {
+            zip = false;
+        } else {
+            zip = Utils.hasExtension(f.getName(), "joz");
+        }
+        setCurrentSession(f, zip);
+    }
+
+    /**
+     * The "Save Session" dialog
+     */
+    public class SessionSaveAsDialog extends ExtendedDialog {
+
+        /**
+         * Constructs a new {@code SessionSaveAsDialog}.
+         */
+        public SessionSaveAsDialog() {
+            super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel"));
+            configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */);
+            initialize();
+            setButtonIcons("save_as", "cancel");
+            setDefaultButton(1);
+            setRememberWindowGeometry(getClass().getName() + ".geometry",
+                    WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450)));
+            setContent(build(), false);
+        }
+
+        /**
+         * Initializes action.
+         */
+        public final void initialize() {
+            layers = new ArrayList<>(getLayerManager().getLayers());
+            exporters = new HashMap<>();
+            dependencies = new MultiMap<>();
+
+            Set<Layer> noExporter = new HashSet<>();
+
+            for (Layer layer : layers) {
+                SessionLayerExporter exporter = null;
+                try {
+                    exporter = SessionWriter.getSessionLayerExporter(layer);
+                } catch (IllegalArgumentException | JosmRuntimeException e) {
+                    Logging.error(e);
+                }
+                if (exporter != null) {
+                    exporters.put(layer, exporter);
+                    Collection<Layer> deps = exporter.getDependencies();
+                    if (deps != null) {
+                        dependencies.putAll(layer, deps);
+                    } else {
+                        dependencies.putVoid(layer);
+                    }
+                } else {
+                    noExporter.add(layer);
+                    exporters.put(layer, null);
+                }
+            }
+
+            int numNoExporter = 0;
+            WHILE: while (numNoExporter != noExporter.size()) {
+                numNoExporter = noExporter.size();
+                for (Layer layer : layers) {
+                    if (noExporter.contains(layer)) continue;
+                    for (Layer depLayer : dependencies.get(layer)) {
+                        if (noExporter.contains(depLayer)) {
+                            noExporter.add(layer);
+                            exporters.put(layer, null);
+                            break WHILE;
+                        }
+                    }
+                }
+            }
+        }
+
+        protected final Component build() {
+            JPanel op = new JPanel(new GridBagLayout());
+            JPanel ip = new JPanel(new GridBagLayout());
+            for (Layer layer : layers) {
+                Component exportPanel;
+                SessionLayerExporter exporter = exporters.get(layer);
+                if (exporter == null) {
+                    if (!exporters.containsKey(layer)) throw new AssertionError();
+                    exportPanel = getDisabledExportPanel(layer);
+                } else {
+                    exportPanel = exporter.getExportPanel();
+                }
+                if (exportPanel == null) continue;
+                JPanel wrapper = new JPanel(new GridBagLayout());
+                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
+                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
+                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
+            }
+            ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
+            JScrollPane sp = new JScrollPane(ip);
+            sp.setBorder(BorderFactory.createEmptyBorder());
+            JPanel p = new JPanel(new GridBagLayout());
+            p.add(sp, GBC.eol().fill());
+            final JTabbedPane tabs = new JTabbedPane();
+            tabs.addTab(tr("Layers"), p);
+            op.add(tabs, GBC.eol().fill());
+            JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
+            chkSaveLocal.addChangeListener(l -> {
+                SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected());
+            });
+            op.add(chkSaveLocal);
+            return op;
+        }
+
+        protected final Component getDisabledExportPanel(Layer layer) {
+            JPanel p = new JPanel(new GridBagLayout());
+            JCheckBox include = new JCheckBox();
+            include.setEnabled(false);
+            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
+            lbl.setToolTipText(tr("No exporter for this layer"));
+            lbl.setLabelFor(include);
+            lbl.setEnabled(false);
+            p.add(include, GBC.std());
+            p.add(lbl, GBC.std());
+            p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
+            return p;
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(MainApplication.isDisplayingMapView());
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void layerAdded(LayerAddEvent e) {
+        // not used
+    }
+
+    @Override
+    public void layerRemoving(LayerRemoveEvent e) {
+        if (e.isLastLayer()) { //TODO CHECK
+            setCurrentSession(null, false);
+        }
+    }
+
+    @Override
+    public void layerOrderChanged(LayerOrderChangeEvent e) {
+        // not used
+    }
+
+    /**
+     * Sets the current session file and the layers included in that file
+     * @param file file
+     * @param zip if it is a zip session file
+     * @param layers layers that are currently represented in the session file
+     */
+    public static void setCurrentSession(File file, boolean zip, List<Layer> layers) {
+        setCurrentLayers(layers);
+        setCurrentSession(file, zip);
+    }
+
+    /**
+     * Sets the current session file
+     * @param file file
+     * @param zip if it is a zip session file
+     */
+    public static void setCurrentSession(File file, boolean zip) {
+        sessionFile = file;
+        isZipSessionFile = zip;
+        if (file == null) {
+            tooltip = TOOLTIP_DEFAULT;
+        } else {
+            tooltip = tr("Save the current session file \"{0}\".", file.getName());
+        }
+        getInstance().setTooltip(tooltip);
+    }
+
+    /**
+     * Sets the layers that are currently represented in the session file
+     * @param layers layers
+     */
+    public static void setCurrentLayers(List<Layer> layers) {
+        layersInSessionFile = layers.stream()
+                .filter(l -> l instanceof AbstractModifiableLayer)
+                .map(WeakReference::new)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns the tooltip for the component
+     * @return the tooltip for the component
+     */
+    public static String getTooltip() {
+        return tooltip;
+    }
+
+}
Index: src/org/openstreetmap/josm/actions/SessionSaveAsAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SessionSaveAsAction.java	(revision 18392)
+++ src/org/openstreetmap/josm/actions/SessionSaveAsAction.java	(working copy)
@@ -4,66 +4,19 @@
 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import javax.swing.BorderFactory;
-import javax.swing.JCheckBox;
-import javax.swing.JFileChooser;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
-import javax.swing.SwingConstants;
-import javax.swing.border.EtchedBorder;
-import javax.swing.filechooser.FileFilter;
+import java.awt.event.KeyEvent;
 
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.gui.MapFrameListener;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.util.WindowGeometry;
-import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
-import org.openstreetmap.josm.io.session.SessionLayerExporter;
-import org.openstreetmap.josm.io.session.SessionWriter;
-import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.UserCancelException;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
- * Saves a JOSM session
+ * Saves a JOSM session to a new file
  * @since 4685
  */
-public class SessionSaveAsAction extends DiskAccessAction implements MapFrameListener {
-
-    private transient List<Layer> layers;
-    private transient Map<Layer, SessionLayerExporter> exporters;
-    private transient MultiMap<Layer, Layer> dependencies;
-
-    private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
+public class SessionSaveAsAction extends SessionSaveAction {
 
     /**
      * Constructs a new {@code SessionSaveAsAction}.
@@ -79,8 +32,12 @@
      * @param installAdapters False, if you don't want to install layer changed and selection changed adapters
      */
     protected SessionSaveAsAction(boolean toolbar, boolean installAdapters) {
+
         super(tr("Save Session As..."), "session", tr("Save the current session to a new file."),
-                null, toolbar, "save_as-session", installAdapters);
+                Shortcut.registerShortcut("system:savesessionas", tr("File: {0}", tr("Save Session As...")),
+                        KeyEvent.VK_S, Shortcut.ALT_CTRL_SHIFT),
+                toolbar, "save_as-session", installAdapters);
+
         setHelpId(ht("/Action/SessionSaveAs"));
         MainApplication.addMapFrameListener(this);
     }
@@ -88,255 +45,10 @@
     @Override
     public void actionPerformed(ActionEvent e) {
         try {
-            saveSession();
+            saveSession(true, false);
         } catch (UserCancelException ignore) {
             Logging.trace(ignore);
         }
     }
 
-    @Override
-    public void destroy() {
-        MainApplication.removeMapFrameListener(this);
-        super.destroy();
-    }
-
-    /**
-     * Attempts to save the session.
-     * @throws UserCancelException when the user has cancelled the save process.
-     * @since 8913
-     */
-    public void saveSession() throws UserCancelException {
-        if (!isEnabled()) {
-            return;
-        }
-
-        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
-        dlg.showDialog();
-        if (dlg.getValue() != 1) {
-            throw new UserCancelException();
-        }
-
-        boolean zipRequired = layers.stream().map(l -> exporters.get(l))
-                .anyMatch(ex -> ex != null && ex.requiresZip());
-
-        FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
-        FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
-
-        AbstractFileChooser fc;
-
-        if (zipRequired) {
-            fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
-        } else {
-            fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,
-                    JFileChooser.FILES_ONLY, "lastDirectory");
-        }
-
-        if (fc == null) {
-            throw new UserCancelException();
-        }
-
-        File file = fc.getSelectedFile();
-        String fn = file.getName();
-
-        boolean zip;
-        FileFilter ff = fc.getFileFilter();
-        if (zipRequired || joz.equals(ff)) {
-            zip = true;
-        } else if (jos.equals(ff)) {
-            zip = false;
-        } else {
-            if (Utils.hasExtension(fn, "joz")) {
-                zip = true;
-            } else {
-                zip = false;
-            }
-        }
-        if (fn.indexOf('.') == -1) {
-            file = new File(file.getPath() + (zip ? ".joz" : ".jos"));
-            if (!SaveActionBase.confirmOverwrite(file)) {
-                throw new UserCancelException();
-            }
-        }
-
-        // TODO: resolve dependencies for layers excluded by the user
-        List<Layer> layersOut = layers.stream()
-                .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())
-                .collect(Collectors.toList());
-
-        Stream<Layer> layersToSaveStream = layersOut.stream()
-                .filter(layer -> layer.isSavable()
-                        && layer instanceof AbstractModifiableLayer
-                        && ((AbstractModifiableLayer) layer).requiresSaveToFile()
-                        && exporters.get(layer) != null
-                        && !exporters.get(layer).requiresZip());
-
-        if (SAVE_LOCAL_FILES_PROPERTY.get()) {
-            // individual files must be saved before the session file as the location may change
-            if (layersToSaveStream
-                .map(layer -> SaveAction.getInstance().doSave(layer, true))
-                .collect(Collectors.toList()) // force evaluation of all elements
-                .contains(false)) {
-
-                new Notification(tr("Not all local files referenced by the session file could be saved."
-                        + "<br>Make sure you save them before closing JOSM."))
-                    .setIcon(JOptionPane.WARNING_MESSAGE)
-                    .setDuration(Notification.TIME_LONG)
-                    .show();
-            }
-        } else if (layersToSaveStream.anyMatch(l -> true)) {
-            new Notification(tr("Not all local files referenced by the session file are saved yet."
-                    + "<br>Make sure you save them before closing JOSM."))
-                .setIcon(JOptionPane.INFORMATION_MESSAGE)
-                .setDuration(Notification.TIME_LONG)
-                .show();
-        }
-
-        int active = -1;
-        Layer activeLayer = getLayerManager().getActiveLayer();
-        if (activeLayer != null) {
-            active = layersOut.indexOf(activeLayer);
-        }
-
-        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip);
-        try {
-            Notification savingNotification = showSavingNotification(file.getName());
-            sw.write(file);
-            SaveActionBase.addToFileOpenHistory(file);
-            showSavedNotification(savingNotification, file.getName());
-        } catch (IOException ex) {
-            Logging.error(ex);
-            HelpAwareOptionPane.showMessageDialogInEDT(
-                    MainApplication.getMainFrame(),
-                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>",
-                            file.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())),
-                    tr("IO Error"),
-                    JOptionPane.ERROR_MESSAGE,
-                    null
-            );
-        }
-    }
-
-    /**
-     * The "Save Session" dialog
-     */
-    public class SessionSaveAsDialog extends ExtendedDialog {
-
-        /**
-         * Constructs a new {@code SessionSaveAsDialog}.
-         */
-        public SessionSaveAsDialog() {
-            super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel"));
-            configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */);
-            initialize();
-            setButtonIcons("save_as", "cancel");
-            setDefaultButton(1);
-            setRememberWindowGeometry(getClass().getName() + ".geometry",
-                    WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450)));
-            setContent(build(), false);
-        }
-
-        /**
-         * Initializes action.
-         */
-        public final void initialize() {
-            layers = new ArrayList<>(getLayerManager().getLayers());
-            exporters = new HashMap<>();
-            dependencies = new MultiMap<>();
-
-            Set<Layer> noExporter = new HashSet<>();
-
-            for (Layer layer : layers) {
-                SessionLayerExporter exporter = null;
-                try {
-                    exporter = SessionWriter.getSessionLayerExporter(layer);
-                } catch (IllegalArgumentException | JosmRuntimeException e) {
-                    Logging.error(e);
-                }
-                if (exporter != null) {
-                    exporters.put(layer, exporter);
-                    Collection<Layer> deps = exporter.getDependencies();
-                    if (deps != null) {
-                        dependencies.putAll(layer, deps);
-                    } else {
-                        dependencies.putVoid(layer);
-                    }
-                } else {
-                    noExporter.add(layer);
-                    exporters.put(layer, null);
-                }
-            }
-
-            int numNoExporter = 0;
-            WHILE: while (numNoExporter != noExporter.size()) {
-                numNoExporter = noExporter.size();
-                for (Layer layer : layers) {
-                    if (noExporter.contains(layer)) continue;
-                    for (Layer depLayer : dependencies.get(layer)) {
-                        if (noExporter.contains(depLayer)) {
-                            noExporter.add(layer);
-                            exporters.put(layer, null);
-                            break WHILE;
-                        }
-                    }
-                }
-            }
-        }
-
-        protected final Component build() {
-            JPanel op = new JPanel(new GridBagLayout());
-            JPanel ip = new JPanel(new GridBagLayout());
-            for (Layer layer : layers) {
-                JPanel wrapper = new JPanel(new GridBagLayout());
-                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
-                Component exportPanel;
-                SessionLayerExporter exporter = exporters.get(layer);
-                if (exporter == null) {
-                    if (!exporters.containsKey(layer)) throw new AssertionError();
-                    exportPanel = getDisabledExportPanel(layer);
-                } else {
-                    exportPanel = exporter.getExportPanel();
-                }
-                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
-                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
-            }
-            ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
-            JScrollPane sp = new JScrollPane(ip);
-            sp.setBorder(BorderFactory.createEmptyBorder());
-            JPanel p = new JPanel(new GridBagLayout());
-            p.add(sp, GBC.eol().fill());
-            final JTabbedPane tabs = new JTabbedPane();
-            tabs.addTab(tr("Layers"), p);
-            op.add(tabs, GBC.eol().fill());
-            JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
-            chkSaveLocal.addChangeListener(l -> {
-                SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected());
-            });
-            op.add(chkSaveLocal);
-            return op;
-        }
-
-        protected final Component getDisabledExportPanel(Layer layer) {
-            JPanel p = new JPanel(new GridBagLayout());
-            JCheckBox include = new JCheckBox();
-            include.setEnabled(false);
-            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
-            lbl.setToolTipText(tr("No exporter for this layer"));
-            lbl.setLabelFor(include);
-            lbl.setEnabled(false);
-            p.add(include, GBC.std());
-            p.add(lbl, GBC.std());
-            p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
-            return p;
-        }
-    }
-
-    @Override
-    protected void updateEnabledState() {
-        setEnabled(MainApplication.isDisplayingMapView());
-    }
-
-    @Override
-    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
-        updateEnabledState();
-    }
 }
Index: src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- src/org/openstreetmap/josm/gui/MainMenu.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/MainMenu.java	(working copy)
@@ -96,6 +96,7 @@
 import org.openstreetmap.josm.actions.SearchNotesDownloadAction;
 import org.openstreetmap.josm.actions.SelectAllAction;
 import org.openstreetmap.josm.actions.SelectNonBranchingWaySequencesAction;
+import org.openstreetmap.josm.actions.SessionSaveAction;
 import org.openstreetmap.josm.actions.SessionSaveAsAction;
 import org.openstreetmap.josm.actions.ShowStatusReportAction;
 import org.openstreetmap.josm.actions.SimplifyWayAction;
@@ -176,8 +177,10 @@
     public final SaveAction save = SaveAction.getInstance();
     /** File / Save As... **/
     public final SaveAsAction saveAs = SaveAsAction.getInstance();
+    /** File / Session &gt; Save Session **/
+    public SessionSaveAction sessionSave = SessionSaveAction.getInstance();
     /** File / Session &gt; Save Session As... **/
-    public SessionSaveAsAction sessionSaveAs;
+    public SessionSaveAsAction sessionSaveAs = new SessionSaveAsAction();
     /** File / Export to GPX... **/
     public final GpxExportAction gpxExport = new GpxExportAction();
     /** File / Download from OSM... **/
@@ -738,8 +741,8 @@
         fileMenu.addSeparator();
         add(fileMenu, save);
         add(fileMenu, saveAs);
-        sessionSaveAs = new SessionSaveAsAction();
-        ExpertToggleAction.addVisibilitySwitcher(fileMenu.add(sessionSaveAs));
+        add(fileMenu, sessionSave, true);
+        add(fileMenu, sessionSaveAs, true);
         add(fileMenu, gpxExport, true);
         fileMenu.addSeparator();
         add(fileMenu, download);
Index: src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(working copy)
@@ -40,7 +40,8 @@
 import javax.swing.event.TableModelEvent;
 import javax.swing.event.TableModelListener;
 
-import org.openstreetmap.josm.actions.SessionSaveAsAction;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.SessionSaveAction;
 import org.openstreetmap.josm.actions.UploadAction;
 import org.openstreetmap.josm.gui.ExceptionDialogUtil;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -92,7 +93,7 @@
     private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer();
 
     private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction();
-    private final SaveSessionAction saveSessionAction = new SaveSessionAction();
+    private final SaveSessionButtonAction saveSessionAction = new SaveSessionButtonAction();
     private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction();
     private final CancelAction cancelAction = new CancelAction();
     private transient SaveAndUploadTask saveAndUploadTask;
@@ -432,18 +433,19 @@
         }
     }
 
-    class SaveSessionAction extends SessionSaveAsAction {
+    class SaveSessionButtonAction extends JosmAction {
 
-        SaveSessionAction() {
-            super(false, false);
+        SaveSessionButtonAction() {
+            super(tr("Save Session"), "session", SessionSaveAction.getTooltip(), null, false, null, false);
         }
 
         @Override
         public void actionPerformed(ActionEvent e) {
             try {
-                saveSession();
-                setUserAction(UserAction.PROCEED);
-                closeDialog();
+                if (SessionSaveAction.getInstance().saveSession(false, true)) {
+                    setUserAction(UserAction.PROCEED);
+                    closeDialog();
+                }
             } catch (UserCancelException ignore) {
                 Logging.trace(ignore);
             }
Index: src/org/openstreetmap/josm/gui/layer/LayerManager.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/LayerManager.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/layer/LayerManager.java	(working copy)
@@ -131,7 +131,7 @@
         LayerRemoveEvent(LayerManager source, Layer removedLayer) {
             super(source);
             this.removedLayer = removedLayer;
-            this.lastLayer = source.getLayers().size() == 1;
+            this.lastLayer = source.getLayers().isEmpty();
         }
 
         /**
Index: src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -758,8 +758,9 @@
      * @return GPX data
      */
     public static GpxData toGpxData(DataSet data, File file) {
-        GpxData gpxData = new GpxData();
+        GpxData gpxData = new GpxData(true);
         fillGpxData(gpxData, data, file, GpxConstants.GPX_PREFIX);
+        gpxData.endUpdate();
         return gpxData;
     }
 
@@ -1010,11 +1011,13 @@
 
         @Override
         public void actionPerformed(ActionEvent e) {
+            String name = getName().replaceAll("^" + tr("Converted from: {0}", ""), "");
             final GpxData gpxData = toGpxData();
-            final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", getName()));
+            final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", name), true);
             if (getAssociatedFile() != null) {
                 String filename = getAssociatedFile().getName().replaceAll(Pattern.quote(".gpx.osm") + '$', "") + ".gpx";
                 gpxLayer.setAssociatedFile(new File(getAssociatedFile().getParentFile(), filename));
+                gpxLayer.getGpxData().setModified(true);
             }
             MainApplication.getLayerManager().addLayer(gpxLayer, false);
             if (Config.getPref().getBoolean("marker.makeautomarkers", true) && !gpxData.waypoints.isEmpty()) {
Index: src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java	(working copy)
@@ -71,7 +71,8 @@
             if (err > 0) {
                 SimplifyWayAction.simplifyWays(ways, err);
             }
-            final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", layer.getName()), null);
+            String name = layer.getName().replaceAll("^" + tr("Converted from: {0}", ""), "");
+            final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", name), null);
             if (layer.getAssociatedFile() != null) {
                 osmLayer.setAssociatedFile(new File(layer.getAssociatedFile().getParentFile(),
                         layer.getAssociatedFile().getName() + ".osm"));
Index: src/org/openstreetmap/josm/io/session/GenericSessionExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(working copy)
@@ -191,6 +191,10 @@
             String zipPath = "layers/" + String.format("%02d", support.getLayerIndex()) + "/data." + extension;
             file.appendChild(support.createTextNode(zipPath));
             addDataFile(support.getOutputStreamZip(zipPath));
+            layer.setAssociatedFile(null);
+            if (layer instanceof AbstractModifiableLayer) {
+                ((AbstractModifiableLayer) layer).onPostSaveToFile();
+            }
         } else {
             try {
                 File f = layer.getAssociatedFile();
Index: src/org/openstreetmap/josm/tools/ListenerList.java
===================================================================
--- src/org/openstreetmap/josm/tools/ListenerList.java	(revision 18392)
+++ src/org/openstreetmap/josm/tools/ListenerList.java	(working copy)
@@ -143,7 +143,7 @@
      * @return <code>true</code> if any are registered.
      */
     public boolean hasListeners() {
-        return !listeners.isEmpty();
+        return !listeners.isEmpty() || weakListeners.stream().map(l -> l.listener.get()).anyMatch(Objects::nonNull);
     }
 
     /**
Index: test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java	(nonexistent)
+++ test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java	(working copy)
@@ -0,0 +1,77 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.io.session.SessionWriterTest;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests for class {@link SessionSaveAsAction}.
+ */
+class SessionSaveActionTest {
+    /**
+     * Setup test.
+     */
+    @RegisterExtension
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().main().projection();
+
+    /**
+     * Unit test of {@link SessionSaveAction}
+     * @throws IOException Temp file could not be created
+     */
+    @Test
+    void testSaveAction() throws IOException {
+        TestUtils.assumeWorkingJMockit();
+
+        File jos = File.createTempFile("session", ".jos");
+        File joz = new File(jos.getAbsolutePath().replaceFirst(".jos$", ".joz"));
+        assertTrue(jos.exists());
+        assertFalse(joz.exists());
+
+        String overrideStr = "javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,"
+                + "preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,"
+                + "labelFor=,text=<html>The following layer has been removed since the session was last saved:<ul><li>OSM layer name</ul>"
+                + "<br>You are about to overwrite the session file \"" + joz.getName()
+                + "\". Would you like to proceed?,verticalAlignment=CENTER,verticalTextPosition=CENTER]";
+
+        SessionSaveAction saveAction = SessionSaveAction.getInstance();
+        saveAction.setEnabled(true);
+
+        OsmDataLayer osm = SessionWriterTest.createOsmLayer();
+        GpxLayer gpx = SessionWriterTest.createGpxLayer();
+
+        JOptionPaneSimpleMocker mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0));
+        SessionSaveAction.setCurrentSession(jos, false, Arrays.asList(gpx, osm)); //gpx and OSM layer
+        MainApplication.getLayerManager().addLayer(gpx); //only gpx layer
+        saveAction.actionPerformed(null); //Complain that OSM layer was removed
+        assertEquals(1, mocker.getInvocationLog().size());
+        assertFalse(jos.exists());
+        assertTrue(joz.exists()); //converted jos to joz since the session includes files
+
+        mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0));
+        joz.delete();
+        saveAction.actionPerformed(null); //Do not complain about removed layers
+        assertEquals(0, mocker.getInvocationLog().size());
+        assertTrue(joz.exists());
+
+        joz.delete();
+    }
+}

Property changes on: test\unit\org\openstreetmap\josm\actions\SessionSaveActionTest.java
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java	(revision 18392)
+++ test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java	(working copy)
@@ -3,8 +3,8 @@
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
-import org.junit.jupiter.api.extension.RegisterExtension;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -19,7 +19,7 @@
      */
     @RegisterExtension
     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules();
+    public JOSMTestRules test = new JOSMTestRules().main();
 
     /**
      * Unit test of {@link SessionSaveAsAction#actionPerformed}
