Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 3377)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 3378)
@@ -362,5 +362,5 @@
 
         I18n.fixJFileChooser();
-        
+
         // init default coordinate format
         //
@@ -508,4 +508,23 @@
     }
 
+    public static boolean exitJosm(boolean exit) {
+        if (Main.saveUnsavedModifications()) {
+            Main.saveGuiGeometry();
+            // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
+            if (Main.isDisplayingMapView()) {
+                Collection<Layer> layers = new ArrayList<Layer>(Main.map.mapView.getAllLayers());
+                for (Layer l: layers) {
+                    Main.map.mapView.removeLayer(l);
+                }
+            }
+            if (exit) {
+                System.exit(0);
+                return true;
+            } else
+                return true;
+        } else
+            return false;
+    }
+
     /**
      * The type of a command line parameter, to be used in switch statements.
Index: trunk/src/org/openstreetmap/josm/actions/ExitAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/ExitAction.java	(revision 3377)
+++ trunk/src/org/openstreetmap/josm/actions/ExitAction.java	(revision 3378)
@@ -2,6 +2,6 @@
 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.gui.help.HelpUtil.ht;
 
 import java.awt.event.ActionEvent;
@@ -27,8 +27,5 @@
 
     public void actionPerformed(ActionEvent e) {
-        if (Main.saveUnsavedModifications()) {
-            Main.saveGuiGeometry();
-            System.exit(0);
-        }
+        Main.exitJosm(true);
     }
 }
Index: trunk/src/org/openstreetmap/josm/data/AutosaveTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/AutosaveTask.java	(revision 3378)
+++ trunk/src/org/openstreetmap/josm/data/AutosaveTask.java	(revision 3378)
@@ -0,0 +1,274 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.regex.Pattern;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
+import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
+import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.OsmExporter;
+import org.openstreetmap.josm.io.OsmReader;
+
+public class AutosaveTask extends TimerTask implements LayerChangeListener, Listener {
+
+    private static final char[] ILLEGAL_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' };
+    private static final String AUTOSAVE_DIR = "autosave";
+    private static final String DELETED_LAYERS_DIR = "autosave/deleted_layers";
+
+
+    public static final IntegerProperty PROP_FILES_PER_LAYER = new IntegerProperty("autosave.filesPerLayer", 1);
+    public static final IntegerProperty PROP_DELETED_LAYERS = new IntegerProperty("autosave.deletedLayersBackupCount", 5);
+    public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("autosave.interval", 5 * 60);
+
+    private static class AutosaveLayerInfo {
+        OsmDataLayer layer;
+        String layerName;
+        String layerFileName;
+        final Deque<File> backupFiles = new LinkedList<File>();
+    }
+
+    //"data layer 1_20100711_1330"
+
+    private final DataSetListenerAdapter datasetAdapter = new DataSetListenerAdapter(this);
+    private Set<DataSet> changedDatasets = new HashSet<DataSet>();
+    private final List<AutosaveLayerInfo> layersInfo = new ArrayList<AutosaveLayerInfo>();
+    private Timer timer;
+    private Object layersLock = new Object();
+    private final Deque<File> deletedLayers = new LinkedList<File>();
+
+    private final File autosaveDir = new File(Main.pref.getPreferencesDir() + AUTOSAVE_DIR);
+    private final File deletedLayersDir = new File(Main.pref.getPreferencesDir() + DELETED_LAYERS_DIR);
+
+    public void schedule() {
+        if (PROP_INTERVAL.get() > 0) {
+
+            if (!autosaveDir.exists() && !autosaveDir.mkdirs()) {
+                System.out.println(tr("Unable to create directory {0}, autosave will be disabled", autosaveDir.getAbsolutePath()));
+                return;
+            }
+            if (!deletedLayersDir.exists() && !deletedLayersDir.mkdirs()) {
+                System.out.println(tr("Unable to create directory {0}, autosave will be disabled", deletedLayersDir.getAbsolutePath()));
+                return;
+            }
+
+            for (File f: deletedLayersDir.listFiles()) {
+                deletedLayers.add(f);
+            }
+
+
+            timer = new Timer(true);
+            timer.schedule(this, 1000, PROP_INTERVAL.get() * 1000);
+            MapView.addLayerChangeListener(this);
+            if (Main.isDisplayingMapView()) {
+                for (OsmDataLayer l: Main.map.mapView.getLayersOfType(OsmDataLayer.class)) {
+                    registerNewlayer(l);
+                }
+            }
+        }
+    }
+
+    private String getFileName(String layerName, int index) {
+        String result = layerName;
+        for (int i=0; i<ILLEGAL_CHARACTERS.length; i++) {
+            result = result.replaceAll(Pattern.quote(String.valueOf(ILLEGAL_CHARACTERS[i])),
+                    '&' + String.valueOf((int)ILLEGAL_CHARACTERS[i]) + ';');
+        }
+        if (index != 0) {
+            result = result + '_' + index;
+        }
+        return result;
+    }
+
+    private void setLayerFileName(AutosaveLayerInfo layer) {
+        int index = 0;
+        while (true) {
+            String filename = getFileName(layer.layer.getName(), index);
+            boolean foundTheSame = false;
+            for (AutosaveLayerInfo info: layersInfo) {
+                if (info != layer && info.layerFileName.equals(filename)) {
+                    foundTheSame = true;
+                    break;
+                }
+            }
+
+            if (!foundTheSame) {
+                layer.layerFileName = filename;
+                return;
+            }
+
+            index++;
+        }
+    }
+
+    private File getNewLayerFile(AutosaveLayerInfo layer) {
+        int index = 0;
+        Date now = new Date();
+        while (true) {
+            File result = new File(autosaveDir, String.format("%1$s_%2$tY%2$tm%2$td_%2$tH%2$tM%3$s.osm", layer.layerFileName, now, index == 0?"":"_" + index));
+            try {
+                if (result.createNewFile())
+                    return result;
+                else {
+                    System.out.println(tr("Unable to create file {0}, other filename will be used", result.getAbsolutePath()));
+                }
+            } catch (IOException e) {
+                System.err.println(tr("IOError while creating file, autosave will be skipped: {0}", e.getMessage()));
+                return null;
+            }
+            index++;
+        }
+    }
+
+    private void savelayer(AutosaveLayerInfo info) throws IOException {
+        if (!info.layer.getName().equals(info.layerName)) {
+            setLayerFileName(info);
+            info.layerName = info.layer.getName();
+        }
+        if (changedDatasets.contains(info.layer.data)) {
+            File file = getNewLayerFile(info);
+            if (file != null) {
+                info.backupFiles.add(file);
+                new OsmExporter().exportData(file, info.layer);
+            }
+        }
+        while (info.backupFiles.size() > PROP_FILES_PER_LAYER.get()) {
+            File oldFile = info.backupFiles.remove();
+            if (!oldFile.delete()) {
+                System.out.println(tr("Unable to delete old backup file {0}", oldFile.getAbsolutePath()));
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        synchronized (layersLock) {
+            try {
+                for (AutosaveLayerInfo info: layersInfo) {
+                    savelayer(info);
+                }
+                changedDatasets.clear();
+            } catch (Throwable t) {
+                // Don't let exception stop time thread
+                System.err.println("Autosave failed: ");
+                t.printStackTrace();
+            }
+        }
+    }
+
+    @Override
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+        // Do nothing
+    }
+
+    private void registerNewlayer(OsmDataLayer layer) {
+        synchronized (layersLock) {
+            layer.data.addDataSetListener(datasetAdapter);
+            AutosaveLayerInfo info = new AutosaveLayerInfo();
+            info.layer = layer;
+            layersInfo.add(info);
+        }
+    }
+
+    @Override
+    public void layerAdded(Layer newLayer) {
+        if (newLayer instanceof OsmDataLayer) {
+            registerNewlayer((OsmDataLayer) newLayer);
+        }
+    }
+
+    @Override
+    public void layerRemoved(Layer oldLayer) {
+        if (oldLayer instanceof OsmDataLayer) {
+            synchronized (layersLock) {
+                OsmDataLayer osmLayer = (OsmDataLayer) oldLayer;
+                osmLayer.data.removeDataSetListener(datasetAdapter);
+                Iterator<AutosaveLayerInfo> it = layersInfo.iterator();
+                while (it.hasNext()) {
+                    AutosaveLayerInfo info = it.next();
+                    if (info.layer == osmLayer) {
+
+                        try {
+                            savelayer(info);
+                            File lastFile = info.backupFiles.pollLast();
+                            if (lastFile != null) {
+                                File backupFile = new File(deletedLayersDir, lastFile.getName());
+                                lastFile.renameTo(backupFile);
+                                deletedLayers.add(lastFile);
+                            }
+                            for (File file: info.backupFiles) {
+                                file.delete();
+                            }
+
+                            while (deletedLayers.size() > PROP_DELETED_LAYERS.get()) {
+                                deletedLayers.remove().delete();
+                            }
+                        } catch (IOException e) {
+                            System.err.println(tr("Error while creating backup of removed layer: {0}", e.getMessage()));
+                        }
+
+                        it.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void processDatasetEvent(AbstractDatasetChangedEvent event) {
+        changedDatasets.add(event.getDataset());
+    }
+
+    public List<OsmDataLayer> getUnsavedLayers() {
+        List<OsmDataLayer> result = new ArrayList<OsmDataLayer>();
+
+        File[] files = autosaveDir.listFiles();
+        if (files == null)
+            return result;
+        for (File f: autosaveDir.listFiles()) {
+            if (f.isDirectory()) {
+                continue;
+            }
+            try {
+                DataSet ds = OsmReader.parseDataSet(new FileInputStream(f), NullProgressMonitor.INSTANCE);
+                String layerName = f.getName();
+                layerName = layerName.substring(0, layerName.lastIndexOf('.'));
+                result.add(new OsmDataLayer(ds, layerName, null));
+                f.renameTo(new File(deletedLayersDir, f.getName()));
+            } catch (FileNotFoundException e) {
+                // Should not happen
+                System.err.println("File " + f.getAbsolutePath() + " not found");
+            } catch (IllegalDataException e) {
+                System.err.println(tr("Unable to read autosaved osm data ({0}) - {1}", f.getAbsoluteFile(), e.getMessage()));
+            }
+        }
+
+        return result;
+    }
+
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 3377)
+++ trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 3378)
@@ -15,4 +15,5 @@
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
@@ -69,5 +70,5 @@
     private Storage<OsmPrimitive> allPrimitives = new Storage<OsmPrimitive>(new IdHash(), 16, true);
     private Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new IdHash());
-    private List<DataSetListener> listeners = new ArrayList<DataSetListener>();
+    private CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<DataSetListener>();
 
     // Number of open calls to beginUpdate
@@ -839,5 +840,5 @@
 
     public void addDataSetListener(DataSetListener dsl) {
-        listeners.add(dsl);
+        listeners.addIfAbsent(dsl);
     }
 
@@ -1001,5 +1002,5 @@
      * Marks all "invisible" objects as deleted. These objects should be always marked as
      * deleted when downloaded from the server. They can be undeleted later if necessary.
-     * 
+     *
      */
     public void deleteInvisible() {
Index: trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 3377)
+++ trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 3378)
@@ -25,4 +25,6 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.AutosaveTask;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
@@ -59,8 +61,5 @@
         mainFrame.addWindowListener(new WindowAdapter(){
             @Override public void windowClosing(final WindowEvent arg0) {
-                if (!Main.saveUnsavedModifications())
-                    return;
-                Main.saveGuiGeometry();
-                System.exit(0);
+                Main.exitJosm(true);
             }
         });
@@ -240,4 +239,27 @@
         }
 
+        AutosaveTask autosaveTask = new AutosaveTask();
+        List<OsmDataLayer> unsavedLayers = autosaveTask.getUnsavedLayers();
+        if (!unsavedLayers.isEmpty()) {
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
+                    tr("Unsaved osm data"),
+                    new String[] {tr("Restore"), tr("Cancel")}
+            );
+            dialog.setContent(tr("JOSM found {0} unsaved osm data layers. It looks like JOSM crashed last time. Do you want to restore data?",
+                    unsavedLayers.size()));
+            dialog.setButtonIcons(new String[] {"ok.png", "cancel.png"});
+            dialog.showDialog();
+            if (dialog.getValue() == 1) {
+                for (OsmDataLayer layer: unsavedLayers) {
+                    Main.main.addLayer(layer);
+                }
+            }
+
+
+        }
+        autosaveTask.schedule();
+
+
         EventQueue.invokeLater(new Runnable() {
             public void run() {
Index: trunk/src/org/openstreetmap/josm/io/OsmExporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmExporter.java	(revision 3377)
+++ trunk/src/org/openstreetmap/josm/io/OsmExporter.java	(revision 3378)
@@ -68,9 +68,14 @@
 
             OsmWriter w = new OsmWriter(new PrintWriter(writer), false, layer.data.getVersion());
-            w.header();
-            w.writeDataSources(layer.data);
-            w.writeContent(layer.data);
-            w.footer();
-            w.close();
+            layer.data.getReadLock().lock();
+            try {
+                w.header();
+                w.writeDataSources(layer.data);
+                w.writeContent(layer.data);
+                w.footer();
+                w.close();
+            } finally {
+                layer.data.getReadLock().unlock();
+            }
             // FIXME - how to close?
             if (!Main.pref.getBoolean("save.keepbackup") && (tmpFile != null)) {
Index: trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 3377)
+++ trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 3378)
@@ -48,5 +48,5 @@
         //System.out.println("Going to handle method "+method+" (short: "+method.getName()+") with event "+args[0]);
         if (method.getName().equals("handleQuit")) {
-            handled = Main.saveUnsavedModifications();
+            handled = Main.exitJosm(false);
         } else if (method.getName().equals("handleAbout")) {
             Main.main.menu.about.actionPerformed(null);
