Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 2025)
@@ -2,4 +2,5 @@
 package org.openstreetmap.josm;
 import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
 
 import java.awt.BorderLayout;
@@ -12,5 +13,7 @@
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
@@ -22,4 +25,5 @@
 import javax.swing.JComponent;
 import javax.swing.JFrame;
+import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
@@ -45,4 +49,5 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.download.DownloadDialog.DownloadTask;
+import org.openstreetmap.josm.gui.io.SaveLayersDialog;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
@@ -393,42 +398,68 @@
     }
 
-    public static boolean breakBecauseUnsavedChanges() {
+    public static boolean saveUnsavedModifications() {
+        if (map == null) return true;
+        SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
+        List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
+        for (OsmDataLayer l: Main.map.mapView.getLayersOfType(OsmDataLayer.class)) {
+            if (l.requiresSaveToFile() || l.requiresUploadToServer()) {
+                layersWithUnmodifiedChanges.add(l);
+            }
+        }
+        dialog.prepareForSavingAndUpdatingLayersBeforeExit();
+        if (!layersWithUnmodifiedChanges.isEmpty()) {
+            dialog.getModel().populate(layersWithUnmodifiedChanges);
+            dialog.setVisible(true);
+            switch(dialog.getUserAction()) {
+                case CANCEL: return false;
+                case PROCEED: return true;
+                default: return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Saves all {@see OsmDataLayer}s with an associated file and with unsaved
+     * data modifications.
+     * 
+     * @return true, if the save operation was successful; false, otherwise
+     */
+    public static boolean saveUnsavedModifications_old() {
         Shortcut.savePrefs();
-        if (map != null) {
-            boolean modified = false;
-            boolean uploadedModified = false;
-            for (final Layer l : map.mapView.getAllLayers()) {
-                if (l instanceof OsmDataLayer && ((OsmDataLayer)l).isModified()) {
-                    modified = true;
-                    uploadedModified = ((OsmDataLayer)l).uploadedModified;
-                    break;
-                }
-            }
-            if (modified) {
-                final String msg = uploadedModified ? "\n"
-                        +tr("Hint: Some changes came from uploading new data to the server.") : "";
-                        int result = new ExtendedDialog(parent, tr("Unsaved Changes"),
-                                new javax.swing.JLabel(tr("There are unsaved changes. Discard the changes and continue?")+msg),
-                                new String[] {tr("Save and Exit"), tr("Discard and Exit"), tr("Cancel")},
-                                new String[] {"save.png", "exit.png", "cancel.png"}).getValue();
-
-                        // Save before exiting
-                        if(result == 1) {
-                            Boolean savefailed = false;
-                            for (final Layer l : map.mapView.getAllLayers()) {
-                                if (l instanceof OsmDataLayer && ((OsmDataLayer)l).isModified()) {
-                                    SaveAction save = new SaveAction();
-                                    if(!save.doSave(l)) {
-                                        savefailed = true;
-                                    }
-                                }
-                            }
-                            return savefailed;
-                        }
-                        else if(result != 2) // Cancel exiting unless the 2nd button was clicked
-                            return true;
-            }
-        }
-        return false;
+        if (map == null)
+            return true; // nothing to save, return success
+
+        int numUnsavedLayers = 0;
+        for (final OsmDataLayer l : map.mapView.getLayersOfType(OsmDataLayer.class)) {
+            if (l.requiresSaveToFile()) {
+                numUnsavedLayers++;
+            }
+        }
+        if (numUnsavedLayers == 0)
+            return true; // nothing to save, return success
+
+        String msg = trn(
+                "There are unsaved changes in {0} layer. Discard the changes and continue?",
+                "There are unsaved changes in {0} layers. Discard the changes and continue?",
+                numUnsavedLayers,
+                numUnsavedLayers
+        );
+        int result = new ExtendedDialog(parent, tr("Unsaved Changes"),
+                new JLabel(msg),
+                new String[] {tr("Save and Exit"), tr("Discard and Exit"), tr("Cancel")},
+                new String[] {"save.png", "exit.png", "cancel.png"}).getValue();
+
+        switch(result) {
+            case 2: /* discard and exit */ return true;
+            case 3: /* cancel */ return false;
+        }
+        boolean savefailed = false;
+        for (OsmDataLayer l : map.mapView.getLayersOfType(OsmDataLayer.class)) {
+            if(!new SaveAction().doSave(l)) {
+                savefailed = true;
+            }
+        }
+        return !savefailed;
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/AbstractInfoAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/AbstractInfoAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/AbstractInfoAction.java	(revision 2025)
@@ -52,5 +52,5 @@
         Iterator<OsmPrimitive> it = primitivesToShow.iterator();
         while(it.hasNext()) {
-            if (it.next().id == 0) {
+            if (it.next().getId() == 0) {
                 it.remove();
             }
Index: trunk/src/org/openstreetmap/josm/actions/ApiPreconditionChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/ApiPreconditionChecker.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/ApiPreconditionChecker.java	(revision 2025)
@@ -1,3 +1,2 @@
-// License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.actions;
 
@@ -71,5 +70,5 @@
             for (Entry<String,String> e : osmPrimitive.entrySet()) {
                 if(e.getValue().length() > 255) {
-                    if (osmPrimitive.deleted) {
+                    if (osmPrimitive.isDeleted()) {
                         // if OsmPrimitive is going to be deleted we automatically shorten the
                         // value
@@ -77,5 +76,5 @@
                                 tr("Warning: automatically truncating value of tag ''{0}'' on deleted primitive {1}",
                                         e.getKey(),
-                                        Long.toString(osmPrimitive.id)
+                                        Long.toString(osmPrimitive.getId())
                                 )
                         );
@@ -85,5 +84,5 @@
                     JOptionPane.showMessageDialog(Main.parent,
                             tr("Length of value for tag ''{0}'' on primitive {1} exceeds the max. allowed length {2}. Values length is {3}.",
-                                    e.getKey(), Long.toString(osmPrimitive.id), 255, e.getValue().length()
+                                    e.getKey(), Long.toString(osmPrimitive.getId()), 255, e.getValue().length()
                             ),
                             tr("Precondition Violation"),
@@ -103,5 +102,5 @@
                         tr("{0} nodes in way {1} exceed the max. allowed number of nodes {2}",
                                 ((Way)osmPrimitive).getNodesCount(),
-                                Long.toString(osmPrimitive.id),
+                                Long.toString(osmPrimitive.getId()),
                                 maxNodes
                         ),
Index: trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 2025)
@@ -90,5 +90,5 @@
         HashSet<Relation> relationsUsingWays = new HashSet<Relation>();
         for (Relation r : getCurrentDataSet().relations) {
-            if (r.deleted || r.incomplete) {
+            if (r.isDeleted() || r.incomplete) {
                 continue;
             }
@@ -177,5 +177,5 @@
         for (Way w : selectedWays) {
             modifyWay = w;
-            if (w.id != 0) {
+            if (w.getId() != 0) {
                 break;
             }
Index: trunk/src/org/openstreetmap/josm/actions/ExitAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/ExitAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/ExitAction.java	(revision 2025)
@@ -21,9 +21,9 @@
     public ExitAction() {
         super(tr("Exit"), "exit", tr("Exit the application."),
-        Shortcut.registerShortcut("system:menuexit", tr("Exit"), KeyEvent.VK_Q, Shortcut.GROUP_MENU), true);
+                Shortcut.registerShortcut("system:menuexit", tr("Exit"), KeyEvent.VK_Q, Shortcut.GROUP_MENU), true);
     }
 
     public void actionPerformed(ActionEvent e) {
-        if (!Main.breakBecauseUnsavedChanges()) {
+        if (Main.saveUnsavedModifications()) {
             Main.saveGuiGeometry();
             System.exit(0);
Index: trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/HistoryInfoAction.java	(revision 2025)
@@ -21,5 +21,5 @@
     @Override
     protected  String createInfoUrl(OsmPrimitive primitive) {
-        return getBaseURL() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.id + "/history";
+        return getBaseURL() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.getId() + "/history";
     }
 }
Index: trunk/src/org/openstreetmap/josm/actions/InfoAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/InfoAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/InfoAction.java	(revision 2025)
@@ -21,5 +21,5 @@
     @Override
     protected  String createInfoUrl(OsmPrimitive primitive) {
-        return getBaseURL() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.id;
+        return getBaseURL() + "/" + OsmPrimitiveType.from(primitive).getAPIName() + "/" + primitive.getId();
     }
 }
Index: trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 2025)
@@ -95,5 +95,5 @@
         Node useNode = null;
         for (Node n: selectedNodes) {
-            if (n.id > 0) {
+            if (n.getId() > 0) {
                 useNode = n;
                 break;
@@ -127,5 +127,5 @@
         HashSet<Relation> relationsUsingNodes = new HashSet<Relation>();
         for (Relation r : getCurrentDataSet().relations) {
-            if (r.deleted || r.incomplete) {
+            if (r.isDeleted() || r.incomplete) {
                 continue;
             }
@@ -217,5 +217,5 @@
 
         for (Way w : getCurrentDataSet().ways) {
-            if (w.deleted || w.incomplete || w.getNodesCount() < 1) {
+            if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
                 continue;
             }
Index: trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 2025)
@@ -126,5 +126,5 @@
     protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
         HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
-        for (Class type: new Class[] {Node.class, Way.class, Relation.class}) {
+        for (Class<? extends OsmPrimitive> type: new Class[] {Node.class, Way.class, Relation.class}) {
             if (!getSourceTagsByType(type).isEmpty()) {
                 ret.put(OsmPrimitiveType.from(type), getSourcePrimitivesByType(type).size());
@@ -136,5 +136,5 @@
     protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
         HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
-        for (Class type: new Class[] {Node.class, Way.class, Relation.class}) {
+        for (Class<? extends OsmPrimitive> type: new Class[] {Node.class, Way.class, Relation.class}) {
             int count = getSubcollectionByType(getEditLayer().data.getSelected(), type).size();
             if (count > 0) {
@@ -156,10 +156,8 @@
     protected void pasteFromHomogeneousSource(Collection<? extends OsmPrimitive> targets) {
         TagCollection tc = null;
-        Class sourceType = null;
-        for (Class type : new Class[] {Node.class, Way.class, Relation.class}) {
+        for (Class<? extends OsmPrimitive> type : new Class[] {Node.class, Way.class, Relation.class}) {
             TagCollection tc1 = getSourceTagsByType(type);
             if (!tc1.isEmpty()) {
                 tc = tc1;
-                sourceType = type;
             }
         }
Index: trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 2025)
@@ -72,4 +72,7 @@
             layer.setName(file.getName());
             layer.setAssociatedFile(file);
+            if (layer instanceof OsmDataLayer) {
+                ((OsmDataLayer) layer).onPostSaveToFile();
+            }
             Main.parent.repaint();
         } catch (IOException e) {
@@ -88,23 +91,4 @@
      */
     public boolean checkSaveConditions(Layer layer) {
-        if (layer == null) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("Internal Error: cannot check conditions for no layer. Please report this as a bug."),
-                    tr("Error"),
-                    JOptionPane.ERROR_MESSAGE
-            );
-            return false;
-        }
-        if (Main.map == null) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("No document open so nothing to save."),
-                    tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE
-            );
-            return false;
-        }
-
         if (layer instanceof OsmDataLayer && isDataSetEmpty((OsmDataLayer)layer) && 1 != new ExtendedDialog(Main.parent, tr("Empty document"), tr("The document contains no data."), new String[] {tr("Save anyway"), tr("Cancel")}, new String[] {"save.png", "cancel.png"}).getValue())
             return false;
@@ -144,5 +128,5 @@
     private boolean isDataSetEmpty(OsmDataLayer layer) {
         for (OsmPrimitive osm : layer.data.allNonDeletedPrimitives())
-            if (!osm.deleted || osm.id > 0)
+            if (!osm.isDeleted() || osm.getId() > 0)
                 return false;
         return true;
Index: trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java	(revision 2025)
@@ -102,5 +102,5 @@
             for (Node n : selectedNodes) {
                 for (Way w : getCurrentDataSet().ways) {
-                    if (w.deleted || w.incomplete) {
+                    if (w.isDeleted() || w.incomplete) {
                         continue;
                     }
@@ -293,5 +293,5 @@
 
         for (Relation r : getCurrentDataSet().relations) {
-            if (r.deleted || r.incomplete) {
+            if (r.isDeleted() || r.incomplete) {
                 continue;
             }
Index: trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 2025)
@@ -66,5 +66,5 @@
             int count = 0;
             for (Way w : getCurrentDataSet().ways) {
-                if (w.deleted || w.incomplete || w.getNodesCount() < 1) {
+                if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
                     continue;
                 }
@@ -91,5 +91,5 @@
                 int count = 0;
                 for (Way w : getCurrentDataSet().ways) {
-                    if (w.deleted || w.incomplete || w.getNodesCount() < 1) {
+                    if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
                         continue;
                     }
@@ -321,5 +321,5 @@
         HashSet<String> rolesToReAdd = null;
         for (Relation r : getCurrentDataSet().relations) {
-            if (r.deleted || r.incomplete) {
+            if (r.isDeleted() || r.incomplete) {
                 continue;
             }
@@ -368,5 +368,5 @@
             // modify all ways containing the nodes
             for (Way w : getCurrentDataSet().ways) {
-                if (w.deleted || w.incomplete || w.getNodesCount() < 1) {
+                if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
                     continue;
                 }
Index: trunk/src/org/openstreetmap/josm/actions/UpdateDataAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/UpdateDataAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/UpdateDataAction.java	(revision 2025)
@@ -79,5 +79,5 @@
             // bounds defined? => use the bbox downloader
             //
-            new DownloadOsmTaskList().download(false, areas, new PleaseWaitProgressMonitor());
+            new DownloadOsmTaskList().download(false, areas, new PleaseWaitProgressMonitor(tr("Updating data")));
         }
     }
Index: trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java	(revision 2025)
@@ -166,10 +166,10 @@
         protected void initMultiFetchReaderWithNodes(MultiFetchServerObjectReader reader) {
             for (OsmPrimitive primitive : toUpdate) {
-                if (primitive instanceof Node && primitive.id > 0) {
+                if (primitive instanceof Node && primitive.getId() > 0) {
                     reader.append((Node)primitive);
                 } else if (primitive instanceof Way) {
                     Way way = (Way)primitive;
                     for (Node node: way.getNodes()) {
-                        if (node.id > 0) {
+                        if (node.getId() > 0) {
                             reader.append(node);
                         }
@@ -181,5 +181,5 @@
         protected void initMultiFetchReaderWithWays(MultiFetchServerObjectReader reader) {
             for (OsmPrimitive primitive : toUpdate) {
-                if (primitive instanceof Way && primitive.id > 0) {
+                if (primitive instanceof Way && primitive.getId() > 0) {
                     reader.append((Way)primitive);
                 }
@@ -189,5 +189,5 @@
         protected void initMultiFetchReaderWithRelations(MultiFetchServerObjectReader reader) {
             for (OsmPrimitive primitive : toUpdate) {
-                if (primitive instanceof Relation && primitive.id > 0) {
+                if (primitive instanceof Relation && primitive.getId() > 0) {
                     reader.append((Relation)primitive);
                 }
Index: trunk/src/org/openstreetmap/josm/actions/UploadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/UploadAction.java	(revision 2025)
@@ -26,4 +26,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.APIDataSet;
 import org.openstreetmap.josm.data.conflict.ConflictCollection;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -34,4 +35,5 @@
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
 import org.openstreetmap.josm.gui.historycombobox.SuggestingJHistoryComboBox;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.io.OsmApi;
@@ -108,4 +110,29 @@
     }
 
+    public boolean checkPreUploadConditions(OsmDataLayer layer) {
+        return checkPreUploadConditions(layer, new APIDataSet(layer.data));
+    }
+
+    public boolean checkPreUploadConditions(OsmDataLayer layer, APIDataSet apiData) {
+        ConflictCollection conflicts = layer.getConflicts();
+        if (conflicts !=null && !conflicts.isEmpty()) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("<html>There are unresolved conflicts in layer ''{0}''.<br>"
+                            + "You have to resolve them first.<html>", layer.getName()),
+                            tr("Warning"),
+                            JOptionPane.WARNING_MESSAGE
+            );
+            return false;
+        }
+        // Call all upload hooks in sequence. The upload confirmation dialog
+        // is one of these.
+        for(UploadHook hook : uploadHooks)
+            if(!hook.checkUpload(apiData.getPrimitivesToAdd(), apiData.getPrimitivesToUpdate(), apiData.getPrimitivesToDelete()))
+                return false;
+
+        return true;
+    }
+
     public void actionPerformed(ActionEvent e) {
         if (!isEnabled())
@@ -121,54 +148,17 @@
         }
 
-        ConflictCollection conflicts = Main.map.mapView.getEditLayer().getConflicts();
-        if (conflicts !=null && !conflicts.isEmpty()) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("There are unresolved conflicts. You have to resolve these first."),
-                    tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE
-            );
-            Main.map.conflictDialog.showDialog();
-            return;
-        }
-
-        final LinkedList<OsmPrimitive> add = new LinkedList<OsmPrimitive>();
-        final LinkedList<OsmPrimitive> update = new LinkedList<OsmPrimitive>();
-        final LinkedList<OsmPrimitive> delete = new LinkedList<OsmPrimitive>();
-        for (OsmPrimitive osm : getCurrentDataSet().allPrimitives()) {
-            if (osm.get("josm/ignore") != null) {
-                continue;
-            }
-            if (osm.id == 0 && !osm.deleted) {
-                add.addLast(osm);
-            } else if (osm.modified && !osm.deleted) {
-                update.addLast(osm);
-            } else if (osm.deleted && osm.id != 0) {
-                delete.addFirst(osm);
-            }
-        }
-
-        if (add.isEmpty() && update.isEmpty() && delete.isEmpty()) {
+        APIDataSet apiData = new APIDataSet(Main.main.getCurrentDataSet());
+        if (apiData.isEmpty()) {
             JOptionPane.showMessageDialog(
                     Main.parent,
                     tr("No changes to upload."),
                     tr("Warning"),
-                    JOptionPane.WARNING_MESSAGE
+                    JOptionPane.INFORMATION_MESSAGE
             );
             return;
         }
-
-        // Call all upload hooks in sequence. The upload confirmation dialog
-        // is one of these.
-        for(UploadHook hook : uploadHooks)
-            if(!hook.checkUpload(add, update, delete))
-                return;
-
-        final Collection<OsmPrimitive> all = new LinkedList<OsmPrimitive>();
-        all.addAll(add);
-        all.addAll(update);
-        all.addAll(delete);
-
-        Main.worker.execute(new UploadDiffTask(all));
+        if (!checkPreUploadConditions(Main.map.mapView.getEditLayer(), apiData))
+            return;
+        Main.worker.execute(createUploadTask(Main.map.mapView.getEditLayer(), apiData.getPrimitives()));
     }
 
@@ -235,11 +225,11 @@
         );
         switch(ret) {
-        case JOptionPane.CLOSED_OPTION: return;
-        case JOptionPane.CANCEL_OPTION: return;
-        case 0: synchronizePrimitive(id); break;
-        case 1: synchronizeDataSet(); break;
-        default:
-            // should not happen
-            throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
+            case JOptionPane.CLOSED_OPTION: return;
+            case JOptionPane.CANCEL_OPTION: return;
+            case 0: synchronizePrimitive(id); break;
+            case 1: synchronizeDataSet(); break;
+            default:
+                // should not happen
+                throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
         }
     }
@@ -275,10 +265,10 @@
         );
         switch(ret) {
-        case JOptionPane.CLOSED_OPTION: return;
-        case 1: return;
-        case 0: synchronizeDataSet(); break;
-        default:
-            // should not happen
-            throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
+            case JOptionPane.CLOSED_OPTION: return;
+            case 1: return;
+            case 0: synchronizeDataSet(); break;
+            default:
+                // should not happen
+                throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
         }
     }
@@ -522,14 +512,19 @@
     }
 
-
-    class UploadDiffTask extends  PleaseWaitRunnable {
+    public UploadDiffTask createUploadTask(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
+        return new UploadDiffTask(layer, toUpload);
+    }
+
+    public class UploadDiffTask extends  PleaseWaitRunnable {
         private boolean uploadCancelled = false;
         private Exception lastException = null;
         private Collection <OsmPrimitive> toUpload;
         private OsmServerWriter writer;
-
-        public UploadDiffTask(Collection <OsmPrimitive> toUpload) {
-            super(tr("Uploading"),false /* don't ignore exceptions */);
+        private OsmDataLayer layer;
+
+        private UploadDiffTask(OsmDataLayer layer, Collection <OsmPrimitive> toUpload) {
+            super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);
             this.toUpload = toUpload;
+            this.layer = layer;
         }
 
@@ -537,5 +532,6 @@
             writer = new OsmServerWriter();
             try {
-                writer.uploadOsm(getCurrentDataSet().version, toUpload, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
+                ProgressMonitor monitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
+                writer.uploadOsm(layer.data.version, toUpload, monitor);
             } catch (Exception sxe) {
                 if (uploadCancelled) {
@@ -554,9 +550,11 @@
             // partially uploaded
             //
-            getEditLayer().cleanupAfterUpload(writer.getProcessedPrimitives());
-            DataSet.fireSelectionChanged(getEditLayer().data.getSelected());
-            getEditLayer().fireDataChange();
+            layer.cleanupAfterUpload(writer.getProcessedPrimitives());
+            DataSet.fireSelectionChanged(layer.data.getSelected());
+            layer.fireDataChange();
             if (lastException != null) {
                 handleFailedUpload(lastException);
+            } else {
+                layer.onPostUploadToServer();
             }
         }
@@ -568,4 +566,16 @@
             }
         }
+
+        public boolean isSuccessful() {
+            return !isCancelled() && !isFailed();
+        }
+
+        public boolean isCancelled() {
+            return uploadCancelled;
+        }
+
+        public boolean isFailed() {
+            return lastException != null;
+        }
     }
 }
Index: trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 2025)
@@ -765,5 +765,5 @@
         Way way = null;
         for (Way w : getCurrentDataSet().ways) {
-            if (w.deleted || w.incomplete || w.getNodesCount() < 1) {
+            if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
                 continue;
             }
Index: trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 2025)
@@ -227,42 +227,42 @@
 
             switch (mode) {
-            case NONE:
-                return false;
-            case MISSING_KEY:
-                return osm.get(key) == null;
-            case ANY:
-                return true;
-            case ANY_VALUE:
-                return osm.get(key) != null;
-            case ANY_KEY:
-                for (String v:osm.getKeys().values()) {
-                    if (v.equals(value))
-                        return true;
-                }
-                return false;
-            case EXACT:
-                return value.equals(osm.get(key));
-            case ANY_KEY_REGEXP:
-                for (String v:osm.getKeys().values()) {
-                    if (valuePattern.matcher(v).matches())
-                        return true;
-                }
-                return false;
-            case ANY_VALUE_REGEXP:
-            case EXACT_REGEXP:
-                for (Entry<String, String> entry:osm.entrySet()) {
-                    if (keyPattern.matcher(entry.getKey()).matches()) {
-                        if (mode == Mode.ANY_VALUE_REGEXP
-                                || valuePattern.matcher(entry.getValue()).matches())
+                case NONE:
+                    return false;
+                case MISSING_KEY:
+                    return osm.get(key) == null;
+                case ANY:
+                    return true;
+                case ANY_VALUE:
+                    return osm.get(key) != null;
+                case ANY_KEY:
+                    for (String v:osm.getKeys().values()) {
+                        if (v.equals(value))
                             return true;
                     }
-                }
-                return false;
-            case MISSING_KEY_REGEXP:
-                for (String k:osm.keySet()) {
-                    if (keyPattern.matcher(k).matches())
-                        return false;
-                }
-                return true;
+                    return false;
+                case EXACT:
+                    return value.equals(osm.get(key));
+                case ANY_KEY_REGEXP:
+                    for (String v:osm.getKeys().values()) {
+                        if (valuePattern.matcher(v).matches())
+                            return true;
+                    }
+                    return false;
+                case ANY_VALUE_REGEXP:
+                case EXACT_REGEXP:
+                    for (Entry<String, String> entry:osm.entrySet()) {
+                        if (keyPattern.matcher(entry.getKey()).matches()) {
+                            if (mode == Mode.ANY_VALUE_REGEXP
+                                    || valuePattern.matcher(entry.getValue()).matches())
+                                return true;
+                        }
+                    }
+                    return false;
+                case MISSING_KEY_REGEXP:
+                    for (String k:osm.keySet()) {
+                        if (keyPattern.matcher(k).matches())
+                            return false;
+                    }
+                    return true;
             }
             throw new AssertionError("Missed state");
@@ -402,5 +402,5 @@
     private static class Modified extends Match {
         @Override public boolean match(OsmPrimitive osm) {
-            return osm.modified || osm.id == 0;
+            return osm.isModified() || osm.getId() == 0;
         }
         @Override public String toString() {return "modified";}
Index: trunk/src/org/openstreetmap/josm/actions/search/SelectionWebsiteLoader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/search/SelectionWebsiteLoader.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/actions/search/SelectionWebsiteLoader.java	(revision 2025)
@@ -48,5 +48,5 @@
             Map<Long, String> ids = idReader.parseIds(in);
             for (OsmPrimitive osm : Main.main.getCurrentDataSet().allNonDeletedPrimitives()) {
-                if (ids.containsKey(osm.id) && osm.getClass().getName().toLowerCase().endsWith(ids.get(osm.id))) {
+                if (ids.containsKey(osm.getId()) && osm.getClass().getName().toLowerCase().endsWith(ids.get(osm.getId()))) {
                     if (mode == SearchAction.SearchMode.remove) {
                         sel.remove(osm);
Index: trunk/src/org/openstreetmap/josm/command/ChangeCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/ChangeCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/ChangeCommand.java	(revision 2025)
@@ -36,5 +36,5 @@
         super.executeCommand();
         osm.cloneFrom(newOsm);
-        osm.modified = true;
+        osm.setModified(true);
         return true;
     }
@@ -47,7 +47,7 @@
         String msg = "";
         switch(OsmPrimitiveType.from(osm)) {
-        case NODE: msg = marktr("Change node {0}"); break;
-        case WAY: msg = marktr("Change way {0}"); break;
-        case RELATION: msg = marktr("Change relation {0}"); break;
+            case NODE: msg = marktr("Change node {0}"); break;
+            case WAY: msg = marktr("Change way {0}"); break;
+            case RELATION: msg = marktr("Change relation {0}"); break;
         }
         return new DefaultMutableTreeNode(
Index: trunk/src/org/openstreetmap/josm/command/ChangePropertyCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/ChangePropertyCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/ChangePropertyCommand.java	(revision 2025)
@@ -77,10 +77,10 @@
         if (value == null) {
             for (OsmPrimitive osm : objects) {
-                osm.modified = true;
+                osm.setModified(true);
                 osm.remove(key);
             }
         } else {
             for (OsmPrimitive osm : objects) {
-                osm.modified = true;
+                osm.setModified(true);
                 osm.put(key, value);
             }
Index: trunk/src/org/openstreetmap/josm/command/ChangeRelationMemberRoleCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/ChangeRelationMemberRoleCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/ChangeRelationMemberRoleCommand.java	(revision 2025)
@@ -51,6 +51,6 @@
         relation.getMember(position).role = newRole;
 
-        oldModified = relation.modified;
-        relation.modified = true;
+        oldModified = relation.isModified();
+        relation.setModified(true);
         return true;
     }
@@ -58,5 +58,5 @@
     @Override public void undoCommand() {
         relation.getMember(position).role = oldRole;
-        relation.modified = oldModified;
+        relation.setModified(oldModified);
     }
 
Index: trunk/src/org/openstreetmap/josm/command/Command.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/Command.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/Command.java	(revision 2025)
@@ -89,5 +89,4 @@
             e.getKey().cloneFrom(e.getValue());
         }
-        getLayer().setModified(true);
     }
 
@@ -114,9 +113,9 @@
         if (o != null)
             return o;
-        Main.debug("unable to find osm with id: " + osm.id + " hashCode: " + osm.hashCode());
+        Main.debug("unable to find osm with id: " + osm.getId() + " hashCode: " + osm.hashCode());
         for (OsmPrimitive t : cloneMap.keySet()) {
             OsmPrimitive to = cloneMap.get(t);
-            Main.debug("now: " + t.id + " hashCode: " + t.hashCode());
-            Main.debug("orig: " + to.id + " hashCode: " + to.hashCode());
+            Main.debug("now: " + t.getId() + " hashCode: " + t.hashCode());
+            Main.debug("orig: " + to.getId() + " hashCode: " + to.hashCode());
         }
         return o;
Index: trunk/src/org/openstreetmap/josm/command/CoordinateConflictResolveCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/CoordinateConflictResolveCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/CoordinateConflictResolveCommand.java	(revision 2025)
@@ -45,5 +45,5 @@
         return new DefaultMutableTreeNode(
                 new JLabel(
-                        tr("Resolve conflicts in coordinates in {0}",conflict.getMy().id),
+                        tr("Resolve conflicts in coordinates in {0}",conflict.getMy().getId()),
                         ImageProvider.get("data", "object"),
                         JLabel.HORIZONTAL
Index: trunk/src/org/openstreetmap/josm/command/DeleteCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/DeleteCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/DeleteCommand.java	(revision 2025)
@@ -110,7 +110,7 @@
             String msg = "";
             switch(OsmPrimitiveType.from(primitive)) {
-            case NODE: msg = "Delete node {0}"; break;
-            case WAY: msg = "Delete way {0}"; break;
-            case RELATION:msg = "Delete relation {0}"; break;
+                case NODE: msg = "Delete node {0}"; break;
+                case WAY: msg = "Delete way {0}"; break;
+                case RELATION:msg = "Delete relation {0}"; break;
             }
 
@@ -131,7 +131,7 @@
             apiname = t.getAPIName();
             switch(t) {
-            case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
-            case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
-            case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
+                case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
+                case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
+                case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
             }
         }
@@ -355,5 +355,5 @@
             }
             Way w = (Way) primitive;
-            if (w.id == 0) { // new ways with id == 0 are fine,
+            if (w.getId() == 0) { // new ways with id == 0 are fine,
                 continue; // process existing ways only
             }
@@ -363,5 +363,5 @@
             // nodes ...
             for (Node n : wnew.getNodes()) {
-                if (n.id != 0 || !primitivesToDelete.contains(n)) {
+                if (n.getId() != 0 || !primitivesToDelete.contains(n)) {
                     nodesToKeep.add(n);
                 }
@@ -426,5 +426,5 @@
         if (a != null) {
             for (OsmPrimitive osm : primitivesToDelete) {
-                if (osm instanceof Node && osm.id != 0) {
+                if (osm instanceof Node && osm.getId() != 0) {
                     Node n = (Node) osm;
                     if (!a.contains(n.getCoor())) {
Index: trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java	(revision 2025)
@@ -45,5 +45,5 @@
         return new DefaultMutableTreeNode(
                 new JLabel(
-                        tr("Resolve conflicts in deleted state in {0}",conflict.getMy().id),
+                        tr("Resolve conflicts in deleted state in {0}",conflict.getMy().getId()),
                         ImageProvider.get("data", "object"),
                         JLabel.HORIZONTAL
@@ -62,5 +62,5 @@
 
         if (decision.equals(MergeDecisionType.KEEP_MINE)) {
-            if (conflict.getMy().deleted) {
+            if (conflict.getMy().isDeleted()) {
                 // because my was involved in a conflict it my still be referred
                 // to from a way or a relation. Fix this now.
Index: trunk/src/org/openstreetmap/josm/command/MoveCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/MoveCommand.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/command/MoveCommand.java	(revision 2025)
@@ -69,5 +69,5 @@
             OldState os = new OldState();
             os.latlon = new LatLon(n.getCoor());
-            os.modified = n.modified;
+            os.modified = n.isModified();
             oldState.add(os);
         }
@@ -93,5 +93,5 @@
         for (Node n : nodes) {
             n.setEastNorth(n.getEastNorth().add(x, y));
-            n.modified = true;
+            n.setModified(true);
         }
         return true;
@@ -103,5 +103,5 @@
             OldState os = it.next();
             n.setCoor(os.latlon);
-            n.modified = os.modified;
+            n.setModified(os.modified);
         }
     }
Index: trunk/src/org/openstreetmap/josm/data/APIDataSet.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/APIDataSet.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/data/APIDataSet.java	(revision 2025)
@@ -0,0 +1,116 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+
+/**
+ * Represents a collection of {@see OsmPrimitive}s which should be uploaded to the
+ * API.
+ * The collection is derived from the modified primitives of an {@see DataSet}.
+ * 
+ * FIXME: use to optimize the upload order before uploading, see various tickets in trac
+ * 
+ */
+public class APIDataSet {
+    private LinkedList<OsmPrimitive> toAdd;
+    private LinkedList<OsmPrimitive> toUpdate;
+    private LinkedList<OsmPrimitive> toDelete;
+
+    /**
+     * creates a new empty data set
+     */
+    public APIDataSet() {
+        toAdd = new LinkedList<OsmPrimitive>();
+        toUpdate = new LinkedList<OsmPrimitive>();
+        toDelete = new LinkedList<OsmPrimitive>();
+    }
+
+    /**
+     * initializes the API data set with the modified primitives in <code>ds</ds>
+     * 
+     * @param ds the data set. Ignore, if null.
+     */
+    public void init(DataSet ds) {
+        if (ds == null) return;
+        toAdd.clear();
+        toUpdate.clear();
+        toDelete.clear();
+        if (ds == null)
+            return;
+        for (OsmPrimitive osm :ds.allPrimitives()) {
+            if (osm.get("josm/ignore") != null) {
+                continue;
+            }
+            if (osm.getId() == 0 && !osm.isDeleted()) {
+                toAdd.addLast(osm);
+            } else if (osm.isModified() && !osm.isDeleted()) {
+                toUpdate.addLast(osm);
+            } else if (osm.isDeleted() && osm.getId() != 0) {
+                toDelete.addFirst(osm);
+            }
+        }
+    }
+
+    /**
+     * initializes the API data set with the modified primitives in <code>ds</ds>
+     * 
+     * @param ds the data set. Ignore, if null.
+     */
+    public APIDataSet(DataSet ds) {
+        this();
+        init(ds);
+    }
+
+    /**
+     * Replies true if there are no primitives to upload
+     * 
+     * @return true if there are no primitives to upload
+     */
+    public boolean isEmpty() {
+        return toAdd.isEmpty() && toUpdate.isEmpty() && toDelete.isEmpty();
+    }
+
+    /**
+     * Replies the primitives which should be added to the OSM database
+     * 
+     * @return the primitives which should be added to the OSM database
+     */
+    public List<OsmPrimitive> getPrimitivesToAdd() {
+        return toAdd;
+    }
+
+    /**
+     * Replies the primitives which should be updated in the OSM database
+     * 
+     * @return the primitives which should be updated in the OSM database
+     */
+    public List<OsmPrimitive> getPrimitivesToUpdate() {
+        return toUpdate;
+    }
+
+    /**
+     * Replies the primitives which should be deleted in the OSM database
+     * 
+     * @return the primitives which should be deleted in the OSM database
+     */
+    public List<OsmPrimitive> getPrimitivesToDelete() {
+        return toDelete;
+    }
+
+    /**
+     * Replies all primitives
+     * 
+     * @return all primitives
+     */
+    public List<OsmPrimitive> getPrimitives() {
+        LinkedList<OsmPrimitive> ret = new LinkedList<OsmPrimitive>();
+        ret.addAll(toAdd);
+        ret.addAll(toUpdate);
+        ret.addAll(toDelete);
+        return ret;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java	(revision 2025)
@@ -42,5 +42,4 @@
         if (Main.map != null && Main.map.mapView.getActiveLayer() instanceof OsmDataLayer) {
             OsmDataLayer data = (OsmDataLayer)Main.map.mapView.getActiveLayer();
-            data.setModified(true);
             data.fireDataChange();
         }
@@ -62,5 +61,4 @@
         if (Main.map != null && Main.map.mapView.getActiveLayer() instanceof OsmDataLayer) {
             OsmDataLayer data = (OsmDataLayer)Main.map.mapView.getActiveLayer();
-            data.setModified(data.uploadedModified || !commands.isEmpty());
             data.fireDataChange();
         }
@@ -81,5 +79,4 @@
         if (Main.map != null && Main.map.mapView.getActiveLayer() instanceof OsmDataLayer) {
             OsmDataLayer data = (OsmDataLayer)Main.map.mapView.getActiveLayer();
-            data.setModified(true);
             data.fireDataChange();
         }
Index: trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 2025)
@@ -83,5 +83,5 @@
         Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
         for (OsmPrimitive osm : allPrimitives())
-            if (osm.visible && !osm.deleted) {
+            if (osm.isVisible() && !osm.isDeleted()) {
                 o.add(osm);
             }
@@ -92,5 +92,5 @@
         Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
         for (OsmPrimitive osm : allPrimitives())
-            if (osm.visible && !osm.deleted && !osm.incomplete) {
+            if (osm.isVisible() && !osm.isDeleted() && !osm.incomplete) {
                 o.add(osm);
             }
@@ -101,5 +101,5 @@
         Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
         for (OsmPrimitive osm : allPrimitives())
-            if (osm.visible && !osm.deleted && !osm.incomplete && !(osm instanceof Relation)) {
+            if (osm.isVisible() && !osm.isDeleted() && !osm.incomplete && !(osm instanceof Relation)) {
                 o.add(osm);
             }
@@ -230,5 +230,5 @@
             return sel;
         for (OsmPrimitive osm : list)
-            if (osm.isSelected() && !osm.deleted) {
+            if (osm.isSelected() && !osm.isDeleted()) {
                 sel.add(osm);
             }
@@ -290,10 +290,10 @@
                     String as = h.get(a);
                     if (as == null) {
-                        as = a.getName() != null ? a.getName() : Long.toString(a.id);
+                        as = a.getName() != null ? a.getName() : Long.toString(a.getId());
                         h.put(a, as);
                     }
                     String bs = h.get(b);
                     if (bs == null) {
-                        bs = b.getName() != null ? b.getName() : Long.toString(b.id);
+                        bs = b.getName() != null ? b.getName() : Long.toString(b.getId());
                         h.put(b, bs);
                     }
@@ -320,11 +320,11 @@
             throw new IllegalArgumentException(tr("parameter {0} > 0 required. Got {1}.", "id", id));
         for (OsmPrimitive primitive : nodes) {
-            if (primitive.id == id) return primitive;
+            if (primitive.getId() == id) return primitive;
         }
         for (OsmPrimitive primitive : ways) {
-            if (primitive.id == id) return primitive;
+            if (primitive.getId() == id) return primitive;
         }
         for (OsmPrimitive primitive : relations) {
-            if (primitive.id == id) return primitive;
+            if (primitive.getId() == id) return primitive;
         }
         return null;
@@ -334,11 +334,11 @@
         HashSet<Long> ret = new HashSet<Long>();
         for (OsmPrimitive primitive : nodes) {
-            ret.add(primitive.id);
+            ret.add(primitive.getId());
         }
         for (OsmPrimitive primitive : ways) {
-            ret.add(primitive.id);
+            ret.add(primitive.getId());
         }
         for (OsmPrimitive primitive : relations) {
-            ret.add(primitive.id);
+            ret.add(primitive.getId());
         }
         return ret;
@@ -346,8 +346,8 @@
 
     /**
-     * Replies the set of ids of all complete primitivies (i.e. those with
+     * Replies the set of ids of all complete primitives (i.e. those with
      * ! primitive.incomplete)
      *
-     * @return the set of ids of all complete primitivies
+     * @return the set of ids of all complete primitives
      */
     public Set<Long> getCompletePrimitiveIds() {
@@ -355,15 +355,15 @@
         for (OsmPrimitive primitive : nodes) {
             if (!primitive.incomplete) {
-                ret.add(primitive.id);
+                ret.add(primitive.getId());
             }
         }
         for (OsmPrimitive primitive : ways) {
             if (! primitive.incomplete) {
-                ret.add(primitive.id);
+                ret.add(primitive.getId());
             }
         }
         for (OsmPrimitive primitive : relations) {
             if (! primitive.incomplete) {
-                ret.add(primitive.id);
+                ret.add(primitive.getId());
             }
         }
@@ -451,3 +451,23 @@
         return parents;
     }
+
+    /**
+     * Replies true if there is at least one primitive in this dataset with
+     * {@see OsmPrimitive#isModified()} == <code>true</code>.
+     * 
+     * @return true if there is at least one primitive in this dataset with
+     * {@see OsmPrimitive#isModified()} == <code>true</code>.
+     */
+    public boolean isModified() {
+        for (Node n: nodes) {
+            if (n.isModified()) return true;
+        }
+        for (Way w: ways) {
+            if (w.isModified()) return true;
+        }
+        for (Relation r: relations) {
+            if (r.isModified()) return true;
+        }
+        return false;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2025)
@@ -69,5 +69,9 @@
      * new to the server! To create a new object, call the default constructor of
      * the respective class.
-     */
+     * 
+     * @deprecated use {@see #getId()}. Don't assign an id, create a primitive with
+     * the respective constructors.
+     */
+    @Deprecated
     public long id = 0;
 
@@ -77,10 +81,16 @@
      * Deleted objects are deleted from the server. If the objects are added (id=0),
      * the modified is ignored and the object is added to the server.
-     */
+     * 
+     * @deprecated Please use {@see #setModified()} and {@see #getModified()}
+     */
+    @Deprecated
     public boolean modified = false;
 
     /**
      * <code>true</code>, if the object has been deleted.
-     */
+     * 
+     * @deprecated use {@see #delete()} and {@see #isDeleted()}
+     */
+    @Deprecated
     public boolean deleted = false;
 
@@ -89,5 +99,8 @@
      * introduced with the 0.4 API to be able to communicate deleted objects
      * (they will have visible=false).
-     */
+     * 
+     * @deprecated use {@see #isVisible()} and {@see #setVisible(boolean)}
+     */
+    @Deprecated
     public boolean visible = true;
 
@@ -100,4 +113,6 @@
     /**
      * If set to true, this object is currently selected.
+     * 
+     * @deprecated use {@see #isSelected()} and {@see #setSelected(boolean)}
      */
     @Deprecated
@@ -121,4 +136,69 @@
     public boolean isSelected() {
         return selected;
+    }
+
+    /**
+     * Marks this primitive as being modified.
+     * 
+     * @param modified true, if this primitive is to be modified
+     */
+    public void setModified(boolean modified) {
+        this.modified = modified;
+    }
+
+    /**
+     * Replies <code>true</code> if the object has been modified since it was loaded from
+     * the server. In this case, on next upload, this object will be updated.
+     * 
+     * @return <code>true</code> if the object has been modified since it was loaded from
+     * the server
+     */
+    public boolean isModified() {
+        return modified;
+    }
+
+    /**
+     * Replies <code>true</code>, if the object has been deleted.
+     * 
+     * @return <code>true</code>, if the object has been deleted.
+     * @see #delete(boolean)
+     */
+    public boolean isDeleted() {
+        return deleted;
+    }
+
+    /**
+     * Replies true if this primitive is either unknown to the server (i.e. its id
+     * is 0) or it is known to the server and it hasn't be deleted on the server.
+     * Replies false, if this primitive is known on the server and has been deleted
+     * on the server.
+     * 
+     * @see #setVisible(boolean)
+     */
+    public boolean isVisible() {
+        return visible;
+    }
+
+    /**
+     * Sets whether this primitive is visible, i.e. whether it is known on the server
+     * and not deleted on the server.
+     * 
+     * @see #isVisible()
+     * @throws IllegalStateException thrown if visible is set to false on an primitive with
+     * id==0
+     */
+    public void setVisible(boolean visible) throws IllegalStateException{
+        if (id == 0 && visible == false)
+            throw new IllegalStateException(tr("a primitive with id=0 can't be invisible"));
+        this.visible = visible;
+    }
+
+    /**
+     * Replies the id of this primitive.
+     * 
+     * @return the id of this primitive.
+     */
+    public long getId() {
+        return id;
     }
 
@@ -502,3 +582,6 @@
      */
     public abstract String getDisplayName(NameFormatter formatter);
+
 }
+
+
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/CreateOsmChangeVisitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/CreateOsmChangeVisitor.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/CreateOsmChangeVisitor.java	(revision 2025)
@@ -47,10 +47,10 @@
 
     public void visit(Node n) {
-        if (n.deleted) {
+        if (n.isDeleted()) {
             switchMode("delete");
             osmwriter.setWithBody(false);
             osmwriter.visit(n);
         } else {
-            switchMode((n.id == 0) ? "create" : "modify");
+            switchMode((n.getId() == 0) ? "create" : "modify");
             osmwriter.setWithBody(true);
             osmwriter.visit(n);
@@ -58,10 +58,10 @@
     }
     public void visit(Way w) {
-        if (w.deleted) {
+        if (w.isDeleted()) {
             switchMode("delete");
             osmwriter.setWithBody(false);
             osmwriter.visit(w);
         } else {
-            switchMode((w.id == 0) ? "create" : "modify");
+            switchMode((w.getId() == 0) ? "create" : "modify");
             osmwriter.setWithBody(true);
             osmwriter.visit(w);
@@ -69,10 +69,10 @@
     }
     public void visit(Relation r) {
-        if (r.deleted) {
+        if (r.isDeleted()) {
             switchMode("delete");
             osmwriter.setWithBody(false);
             osmwriter.visit(r);
         } else {
-            switchMode((r.id == 0) ? "create" : "modify");
+            switchMode((r.getId() == 0) ? "create" : "modify");
             osmwriter.setWithBody(true);
             osmwriter.visit(r);
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java	(revision 2025)
@@ -529,5 +529,5 @@
             for (RelationMember m : r.getMembers())
             {
-                if (m.isNode() && !m.getMember().incomplete && !m.getMember().deleted)
+                if (m.isNode() && !m.getMember().incomplete && !m.getMember().isDeleted())
                 {
                     drawSelectedMember(m.getMember(), styles != null ? getPrimitiveStyle(m.getMember()) : null, true, true);
@@ -550,5 +550,5 @@
             for (RelationMember m : r.getMembers())
             {
-                if (m.isWay() && !m.getMember().incomplete && !m.getMember().deleted) /* nodes drawn on second call */
+                if (m.isWay() && !m.getMember().incomplete && !m.getMember().isDeleted()) /* nodes drawn on second call */
                 {
                     drawSelectedMember(m.getMember(), styles != null ? getPrimitiveStyle(m.getMember())
@@ -580,5 +580,5 @@
                 // TODO Nullable member will not be allowed after RelationMember.member is encalupsed
                 r.putError(tr("Empty member in relation."), true);
-            } else if(m.getMember().deleted) {
+            } else if(m.getMember().isDeleted()) {
                 r.putError(tr("Deleted member ''{0}'' in relation.",
                         m.getMember().getDisplayName(DefaultNameFormatter.getInstance())), true);
@@ -834,5 +834,5 @@
                 //TODO Remove useless nullcheck when RelationMember.member is encalupsed
                 r.putError(tr("Empty member in relation."), true);
-            } else if(m.getMember().deleted) {
+            } else if(m.getMember().isDeleted()) {
                 r.putError(tr("Deleted member ''{0}'' in relation.",
                         m.getMember().getDisplayName(DefaultNameFormatter.getInstance())), true);
@@ -1379,5 +1379,5 @@
             for (final Relation osm : data.relations)
             {
-                if(!osm.deleted && !osm.incomplete && osm.mappaintVisibleCode != viewid)
+                if(!osm.isDeleted() && !osm.incomplete && osm.mappaintVisibleCode != viewid)
                 {
                     osm.visit(this);
@@ -1396,5 +1396,5 @@
             for (final Way osm : data.ways)
             {
-                if (!osm.incomplete && !osm.deleted
+                if (!osm.incomplete && !osm.isDeleted()
                         && osm.mappaintVisibleCode != viewid && osm.mappaintDrawnCode != paintid)
                 {
@@ -1437,5 +1437,5 @@
             //    profilerN = 0;
             for (final OsmPrimitive osm : data.ways)
-                if (!osm.incomplete && !osm.deleted && !osm.isSelected()
+                if (!osm.incomplete && !osm.isDeleted() && !osm.isSelected()
                         && osm.mappaintVisibleCode != viewid )
                 {
@@ -1456,5 +1456,5 @@
         //profilerN = 0;
         for (final OsmPrimitive osm : data.getSelected()) {
-            if (!osm.incomplete && !osm.deleted && !(osm instanceof Node)
+            if (!osm.incomplete && !osm.isDeleted() && !(osm instanceof Node)
                     && osm.mappaintVisibleCode != viewid && osm.mappaintDrawnCode != paintid)
             {
@@ -1476,5 +1476,5 @@
         //profilerN = 0;
         for (final OsmPrimitive osm : data.nodes)
-            if (!osm.incomplete && !osm.deleted
+            if (!osm.incomplete && !osm.isDeleted()
                     && osm.mappaintVisibleCode != viewid && osm.mappaintDrawnCode != paintid)
             {
@@ -1496,5 +1496,5 @@
             currentColor = nodeColor;
             for (final OsmPrimitive osm : data.ways)
-                if (!osm.incomplete && !osm.deleted
+                if (!osm.incomplete && !osm.isDeleted()
                         && osm.mappaintVisibleCode != viewid )
                 {
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeSourceBuildingVisitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeSourceBuildingVisitor.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeSourceBuildingVisitor.java	(revision 2025)
@@ -94,5 +94,5 @@
         for (RelationMember member: r.getMembers()) {
             newMembers.add(
-                new RelationMember(member.getRole(), mappedPrimitives.get(member.getMember())));
+                    new RelationMember(member.getRole(), mappedPrimitives.get(member.getMember())));
 
         }
@@ -117,9 +117,9 @@
         OsmPrimitive clone = null;
         if (primitive instanceof Node) {
-            clone = new Node(primitive.id);
+            clone = new Node(primitive.getId());
         } else if (primitive instanceof Way) {
-            clone = new Way(primitive.id);
+            clone = new Way(primitive.getId());
         } else if (primitive instanceof Relation) {
-            clone = new Relation(primitive.id);
+            clone = new Relation(primitive.getId());
         }
         clone.incomplete = true;
@@ -176,5 +176,5 @@
 
     protected boolean isNew(OsmPrimitive primitive) {
-        return primitive.id == 0;
+        return primitive.getId() == 0;
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java	(revision 2025)
@@ -62,12 +62,12 @@
         this.theirDataSet = theirDataSet;
 
-        for (Node n : myDataSet.nodes) if (n.id != 0) {
-            nodeshash.put(n.id, n);
-        }
-        for (Way w : myDataSet.ways) if (w.id != 0) {
-            wayshash.put(w.id, w);
-        }
-        for (Relation r : myDataSet.relations) if (r.id != 0) {
-            relshash.put(r.id, r);
+        for (Node n : myDataSet.nodes) if (n.getId() != 0) {
+            nodeshash.put(n.getId(), n);
+        }
+        for (Way w : myDataSet.ways) if (w.getId() != 0) {
+            wayshash.put(w.getId(), w);
+        }
+        for (Relation r : myDataSet.relations) if (r.getId() != 0) {
+            relshash.put(r.getId(), r);
         }
         conflicts = new ConflictCollection();
@@ -98,5 +98,5 @@
             HashMap<Long, P> primitivesWithDefinedIds) {
 
-        if (other.id > 0 ) {
+        if (other.getId() > 0 ) {
             // try to merge onto a matching primitive with the same
             // defined id
@@ -109,9 +109,9 @@
             //
             for (P my : myPrimitives) {
-                if (my.id >0 ) {
+                if (my.getId() >0 ) {
                     continue;
                 }
                 if (my.hasEqualSemanticAttributes(other)) {
-                    if (my.deleted != other.deleted) {
+                    if (my.isDeleted() != other.isDeleted()) {
                         // differences in deleted state have to be merged manually
                         //
@@ -120,8 +120,8 @@
                         // copy the technical attributes from other
                         // version
-                        my.visible = other.visible;
+                        my.setVisible(other.isVisible());
                         my.user = other.user;
                         my.setTimestamp(other.getTimestamp());
-                        my.modified = other.modified;
+                        my.setModified(other.isModified());
                         merged.put(other, my);
                     }
@@ -186,5 +186,5 @@
             Node mergedNode = (Node) merged.get(myNode);
             if (mergedNode != null) {
-                if (!mergedNode.deleted) {
+                if (!mergedNode.isDeleted()) {
                     newNodes.add(mergedNode);
                 }
@@ -207,5 +207,5 @@
                 newMembers.add(myMember);
             } else {
-                if (! mergedMember.deleted) {
+                if (! mergedMember.isDeleted()) {
                     RelationMember newMember = new RelationMember(myMember.getRole(), mergedMember);
                     newMembers.add(newMember);
@@ -234,8 +234,8 @@
         // merge other into an existing primitive with the same id, if possible
         //
-        if (myPrimitivesWithDefinedIds.containsKey(other.id)) {
-            P my = myPrimitivesWithDefinedIds.get(other.id);
+        if (myPrimitivesWithDefinedIds.containsKey(other.getId())) {
+            P my = myPrimitivesWithDefinedIds.get(other.getId());
             if (my.version <= other.version) {
-                if (! my.visible && other.visible) {
+                if (! my.isVisible() && other.isVisible()) {
                     // should not happen
                     //
@@ -243,8 +243,8 @@
                             + "their primitive with lower version {2} is not visible. "
                             + "Can't deal with this inconsistency. Keeping my primitive. ",
-                            Long.toString(my.id),Long.toString(my.version), Long.toString(other.version)
+                            Long.toString(my.getId()),Long.toString(my.version), Long.toString(other.version)
                     ));
                     merged.put(other, my);
-                } else if (my.visible && ! other.visible) {
+                } else if (my.isVisible() && ! other.isVisible()) {
                     // this is always a conflict because the user has to decide whether
                     // he wants to create a clone of its local primitive or whether he
@@ -270,32 +270,32 @@
                     //
                     merged.put(other, my);
-                } else if (my.deleted && ! other.deleted && my.version == other.version) {
+                } else if (my.isDeleted() && ! other.isDeleted() && my.version == other.version) {
                     // same version, but my is deleted. Assume mine takes precedence
                     // otherwise too many conflicts when refreshing from the server
                     merged.put(other, my);
-                } else if (my.deleted != other.deleted) {
+                } else if (my.isDeleted() != other.isDeleted()) {
                     // differences in deleted state have to be resolved manually
                     //
                     conflicts.add(my,other);
-                } else if (! my.modified && other.modified) {
+                } else if (! my.isModified() && other.isModified()) {
                     // my not modified. We can assume that other is the most recent version.
                     // clone it onto my. But check first, whether other is deleted. if so,
                     // make sure that my is not references anymore in myDataSet.
                     //
-                    if (other.deleted) {
+                    if (other.isDeleted()) {
                         myDataSet.unlinkReferencesToPrimitive(my);
                     }
                     my.cloneFrom(other);
                     merged.put(other, my);
-                } else if (! my.modified && !other.modified && my.version == other.version) {
+                } else if (! my.isModified() && !other.isModified() && my.version == other.version) {
                     // both not modified. Keep mine
                     //
                     merged.put(other,my);
-                } else if (! my.modified && !other.modified && my.version < other.version) {
+                } else if (! my.isModified() && !other.isModified() && my.version < other.version) {
                     // my not modified but other is newer. clone other onto mine.
                     //
                     my.cloneFrom(other);
                     merged.put(other,my);
-                } else if (my.modified && ! other.modified && my.version == other.version) {
+                } else if (my.isModified() && ! other.isModified() && my.version == other.version) {
                     // my is same as other but mine is modified
                     // => keep mine
@@ -312,5 +312,5 @@
                     //
                     my.cloneFrom(other);
-                    my.modified = true;
+                    my.setModified(true);
                     merged.put(other, my);
                 }
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/SimplePaintVisitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/SimplePaintVisitor.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/SimplePaintVisitor.java	(revision 2025)
@@ -56,5 +56,5 @@
     /**
      * Preferences
-    */
+     */
     protected Color inactiveColor;
     protected Color selectedColor;
@@ -124,6 +124,6 @@
 
         ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-            Main.pref.getBoolean("mappaint.use-antialiasing", false) ?
-            RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
+                Main.pref.getBoolean("mappaint.use-antialiasing", false) ?
+                        RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
     }
 
@@ -149,8 +149,8 @@
         //profilerN = 0;
         for (final OsmPrimitive osm : data.relations)
-            if (!osm.deleted && !osm.isSelected())
+            if (!osm.isDeleted() && !osm.isSelected())
             {
                 osm.visit(this);
-        //        profilerN++;
+                //        profilerN++;
             }
 
@@ -163,16 +163,16 @@
         //profilerN = 0;
         for (final OsmPrimitive osm : data.ways)
-            if (!osm.deleted && !osm.isSelected() && osm.isTagged())
+            if (!osm.isDeleted() && !osm.isSelected() && osm.isTagged())
             {
                 osm.visit(this);
-        //        profilerN++;
+                //        profilerN++;
             }
         displaySegments();
 
         for (final OsmPrimitive osm : data.ways)
-            if (!osm.deleted && !osm.isSelected() && !osm.isTagged())
+            if (!osm.isDeleted() && !osm.isSelected() && !osm.isTagged())
             {
                 osm.visit(this);
-        //        profilerN++;
+                //        profilerN++;
             }
         displaySegments();
@@ -187,8 +187,8 @@
         //profilerN = 0;
         for (final OsmPrimitive osm : data.getSelected())
-            if (!osm.deleted)
+            if (!osm.isDeleted())
             {
                 osm.visit(this);
-        //        profilerN++;
+                //        profilerN++;
             }
         displaySegments();
@@ -202,8 +202,8 @@
         //profilerN = 0;
         for (final OsmPrimitive osm : data.nodes)
-            if (!osm.deleted && !osm.isSelected())
+            if (!osm.isDeleted() && !osm.isSelected())
             {
                 osm.visit(this);
-        //        profilerN++;
+                //        profilerN++;
             }
 
@@ -217,19 +217,19 @@
         if(virtualNodeSize != 0)
         {
-        //    profilerN = 0;
+            //    profilerN = 0;
             currentColor = nodeColor;
             for (final OsmPrimitive osm : data.ways)
-                if (!osm.deleted)
-                    {
-                        visitVirtual((Way)osm);
-        //                profilerN++;
-                    }
+                if (!osm.isDeleted())
+                {
+                    visitVirtual((Way)osm);
+                    //                profilerN++;
+                }
             displaySegments();
 
-        //    if(profiler)
-        //    {
-        //        System.out.format("Virtual  : %4dms, n=%5d\n", (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
-        //        profilerLast = java.lang.System.currentTimeMillis();
-        //    }
+            //    if(profiler)
+            //    {
+            //        System.out.format("Virtual  : %4dms, n=%5d\n", (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
+            //        profilerLast = java.lang.System.currentTimeMillis();
+            //    }
         }
 
@@ -249,20 +249,25 @@
         if (n.incomplete) return;
 
-        if (inactive)
+        if (inactive) {
             drawNode(n, inactiveColor, unselectedNodeSize, unselectedNodeRadius, fillUnselectedNode);
-        else if (n.highlighted)
+        } else if (n.highlighted) {
             drawNode(n, highlightColor, selectedNodeSize, selectedNodeRadius, fillSelectedNode);
-        else if (n.isSelected())
+        } else if (n.isSelected()) {
             drawNode(n, selectedColor, selectedNodeSize, selectedNodeRadius, fillSelectedNode);
-        else if(n.isTagged())
+        } else if(n.isTagged()) {
             drawNode(n, nodeColor, taggedNodeSize, taggedNodeRadius, fillUnselectedNode);
-        else
+        } else {
             drawNode(n, nodeColor, unselectedNodeSize, unselectedNodeRadius, fillUnselectedNode);
+        }
     }
 
     public static Boolean isLargeSegment(Point p1, Point p2, int space)
     {
-        int xd = p1.x-p2.x; if(xd < 0) xd = -xd;
-        int yd = p1.y-p2.y; if(yd < 0) yd = -yd;
+        int xd = p1.x-p2.x; if(xd < 0) {
+            xd = -xd;
+        }
+        int yd = p1.y-p2.y; if(yd < 0) {
+            yd = -yd;
+        }
         return (xd+yd > space);
     }
@@ -325,7 +330,8 @@
                 Point p = nc.getPoint(it.next());
                 drawSegment(lastP, p, wayColor,
-                    showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
-                if (showOrderNumber)
+                        showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
+                if (showOrderNumber) {
                     drawOrderNumber(lastP, p, orderNumber);
+                }
                 lastP = p;
             }
@@ -334,5 +340,5 @@
 
     private Stroke relatedWayStroke = new BasicStroke(
-        4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
+            4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
     public void visit(Relation r) {
         if (r.incomplete) return;
@@ -349,10 +355,14 @@
 
         for (RelationMember m : r.getMembers()) {
-            if (m.getMember().incomplete || m.getMember().deleted) continue;
+            if (m.getMember().incomplete || m.getMember().isDeleted()) {
+                continue;
+            }
 
             if (m.isNode()) {
                 Point p = nc.getPoint(m.getNode());
                 if (p.x < 0 || p.y < 0
-                    || p.x > nc.getWidth() || p.y > nc.getHeight()) continue;
+                        || p.x > nc.getWidth() || p.y > nc.getHeight()) {
+                    continue;
+                }
 
                 g.drawOval(p.x-3, p.y-3, 6, 6);
@@ -362,5 +372,7 @@
                 boolean first = true;
                 for (Node n : m.getWay().getNodes()) {
-                    if (n.incomplete || n.deleted) continue;
+                    if (n.incomplete || n.isDeleted()) {
+                        continue;
+                    }
                     Point p = nc.getPoint(n);
                     if (first) {
@@ -418,6 +430,7 @@
                 g.fillRect(p.x - radius, p.y - radius, size, size);
                 g.drawRect(p.x - radius, p.y - radius, size, size);
-            } else
+            } else {
                 g.drawRect(p.x - radius, p.y - radius, size, size);
+            }
         }
     }
@@ -427,5 +440,7 @@
      */
     protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
-        if (col != currentColor) displaySegments(col);
+        if (col != currentColor) {
+            displaySegments(col);
+        }
 
         if (isSegmentVisible(p1, p2)) {
Index: trunk/src/org/openstreetmap/josm/gui/ExtendedDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/ExtendedDialog.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/ExtendedDialog.java	(revision 2025)
@@ -3,4 +3,5 @@
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.Frame;
 import java.awt.GridBagLayout;
 import java.awt.Toolkit;
@@ -202,5 +203,5 @@
         super.setVisible(visible);
         if (visible) {
-            toFront();
+            repaint();
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 2025)
@@ -47,5 +47,5 @@
         mainFrame.addWindowListener(new WindowAdapter(){
             @Override public void windowClosing(final WindowEvent arg0) {
-                if (Main.breakBecauseUnsavedChanges())
+                if (!Main.saveUnsavedModifications())
                     return;
                 Main.saveGuiGeometry();
@@ -73,12 +73,14 @@
         final Map<String, Collection<String>> args = new HashMap<String, Collection<String>>();
         for (String arg : argArray) {
-            if (!arg.startsWith("--"))
+            if (!arg.startsWith("--")) {
                 arg = "--download="+arg;
+            }
             int i = arg.indexOf('=');
             String key = i == -1 ? arg.substring(2) : arg.substring(2,i);
             String value = i == -1 ? "" : arg.substring(i+1);
             Collection<String> v = args.get(key);
-            if (v == null)
+            if (v == null) {
                 v = new LinkedList<String>();
+            }
             v.add(value);
             args.put(key, v);
@@ -88,8 +90,9 @@
 
         // Check if passed as parameter
-        if (args.containsKey("language"))
+        if (args.containsKey("language")) {
             I18n.set((String)(args.get("language").toArray()[0]));
-        else
+        } else {
             I18n.set(Main.pref.get("language", null));
+        }
 
         if (argList.contains("--help") || argList.contains("-?") || argList.contains("-h")) {
@@ -143,7 +146,8 @@
 
         if (((!args.containsKey("no-maximize") && !args.containsKey("geometry")
-        && Main.pref.get("gui.geometry").length() == 0) || args.containsKey("maximize"))
-        && Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH))
+                && Main.pref.get("gui.geometry").length() == 0) || args.containsKey("maximize"))
+                && Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
             mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
+        }
 
         EventQueue.invokeLater(new Runnable() {
@@ -161,11 +165,13 @@
     public static void removeObsoletePreferences() {
         String[] obsolete = {
-           "sample.preference.that.does.not.exist", // sample comment, expiry date should go here
-           "osm-server.version", // remove this around 10/2009
-           "osm-server.additional-versions", // remove this around 10/2009
-           null
+                "sample.preference.that.does.not.exist", // sample comment, expiry date should go here
+                "osm-server.version", // remove this around 10/2009
+                "osm-server.additional-versions", // remove this around 10/2009
+                null
         };
         for (String i : obsolete) {
-            if (i == null) continue;
+            if (i == null) {
+                continue;
+            }
             if (Main.pref.hasKey(i)) {
                 Main.pref.removeFromCollection(i, Main.pref.get(i));
Index: trunk/src/org/openstreetmap/josm/gui/MapStatus.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapStatus.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/MapStatus.java	(revision 2025)
@@ -194,10 +194,10 @@
                             final StringBuilder text = new StringBuilder();
                             String name = osm.getDisplayName(DefaultNameFormatter.getInstance());
-                            if (osm.id == 0 || osm.modified) {
+                            if (osm.getId() == 0 || osm.isModified()) {
                                 name = "<i><b>"+ osm.getDisplayName(DefaultNameFormatter.getInstance())+"*</b></i>";
                             }
                             text.append(name);
-                            if (osm.id != 0) {
-                                text.append("<br>id="+osm.id);
+                            if (osm.getId() != 0) {
+                                text.append("<br>id="+osm.getId());
                             }
                             for (Entry<String, String> e : osm.entrySet()) {
Index: trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 2025)
@@ -44,5 +44,4 @@
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer.ModifiedChangedListener;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
@@ -152,13 +151,4 @@
      */
     public void addLayer(Layer layer) {
-        if (layer instanceof OsmDataLayer) {
-            OsmDataLayer editLayer = (OsmDataLayer)layer;
-            editLayer.listenerModified.add(new ModifiedChangedListener(){
-                public void modifiedChanged(boolean value, OsmDataLayer source) {
-                    JOptionPane.getFrameForComponent(Main.parent).setTitle((value?"*":"")
-                            +tr("Java OpenStreetMap Editor"));
-                }
-            });
-        }
         if (layer instanceof MarkerLayer && playHeadMarker == null) {
             playHeadMarker = PlayHeadMarker.create();
@@ -510,4 +500,7 @@
             }
         }
+        if (layer instanceof OsmDataLayer) {
+            refreshTitle((OsmDataLayer)layer);
+        }
 
         /* This only makes the buttons look disabled. Disabling the actions as well requires
@@ -600,4 +593,19 @@
         if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
             repaint();
+        } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
+                || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
+            OsmDataLayer layer = (OsmDataLayer)evt.getSource();
+            if (layer == getEditLayer()) {
+                refreshTitle(layer);
+            }
+        }
+    }
+
+    protected void refreshTitle(OsmDataLayer layer) {
+        boolean dirty = layer.requiresSaveToFile() || layer.requiresUploadToServer();
+        if (dirty) {
+            JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor"));
+        } else {
+            JOptionPane.getFrameForComponent(Main.parent).setTitle(tr("Java OpenStreetMap Editor"));
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 2025)
@@ -74,5 +74,5 @@
         double dist = getDist100Pixel();
         return dist >= 2000 ? Math.round(dist/100)/10 +" km" : (dist >= 1
-        ? Math.round(dist*10)/10 +" m" : "< 1 m");
+                ? Math.round(dist*10)/10 +" m" : "< 1 m");
     }
 
@@ -112,7 +112,7 @@
                         center.east() - getWidth()/2.0*scale,
                         center.north() - getHeight()/2.0*scale),
-                new EastNorth(
-                        center.east() + getWidth()/2.0*scale,
-                        center.north() + getHeight()/2.0*scale));
+                        new EastNorth(
+                                center.east() + getWidth()/2.0*scale,
+                                center.north() + getHeight()/2.0*scale));
     };
 
@@ -121,5 +121,5 @@
         Bounds b = getProjection().getWorldBoundsLatLon();
         return new ProjectionBounds(getProjection().latlon2eastNorth(b.min),
-            getProjection().latlon2eastNorth(b.max));
+                getProjection().latlon2eastNorth(b.max));
     };
 
@@ -130,7 +130,7 @@
                         center.east() - getWidth()/2.0*scale,
                         center.north() - getHeight()/2.0*scale)),
-                getProjection().eastNorth2latlon(new EastNorth(
-                        center.east() + getWidth()/2.0*scale,
-                        center.north() + getHeight()/2.0*scale)));
+                        getProjection().eastNorth2latlon(new EastNorth(
+                                center.east() + getWidth()/2.0*scale,
+                                center.north() + getHeight()/2.0*scale)));
     };
 
@@ -189,6 +189,7 @@
         if(lon < b.min.lon()) {changed = true; lon = b.min.lon(); }
         else if(lon > b.max.lon()) {changed = true; lon = b.max.lon(); }
-        if(changed)
-          newCenter = new CachedLatLon(lat, lon).getEastNorth();
+        if(changed) {
+            newCenter = new CachedLatLon(lat, lon).getEastNorth();
+        }
         if (!newCenter.equals(center)) {
             EastNorth oldCenter = center;
@@ -211,12 +212,14 @@
             e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.max.lon()));
             d = e2.east() - e1.east();
-            if(d < width*newScale)
+            if(d < width*newScale) {
                 newScale = Math.max(newScaleH, d/width);
+            }
         }
         else
         {
             d = d/(l1.greatCircleDistance(l2)*height*10);
-            if(newScale < d)
+            if(newScale < d) {
                 newScale = d;
+            }
         }
         if (scale != newScale) {
@@ -296,5 +299,5 @@
             return null;
         for (Node n : ds.nodes) {
-            if (n.deleted || n.incomplete) {
+            if (n.isDeleted() || n.incomplete) {
                 continue;
             }
@@ -307,6 +310,6 @@
             // when multiple nodes on one point, prefer new or selected nodes
             else if(dist == minDistanceSq && minPrimitive != null
-                    && ((n.id == 0 && n.isSelected())
-                            || (!minPrimitive.isSelected() && (n.isSelected() || n.id == 0)))) {
+                    && ((n.getId() == 0 && n.isSelected())
+                            || (!minPrimitive.isSelected() && (n.isSelected() || n.getId() == 0)))) {
                 minPrimitive = n;
             }
@@ -327,5 +330,5 @@
             return null;
         for (Way w : ds.ways) {
-            if (w.deleted || w.incomplete) {
+            if (w.isDeleted() || w.incomplete) {
                 continue;
             }
@@ -334,5 +337,5 @@
             for (Node n : w.getNodes()) {
                 i++;
-                if (n.deleted || n.incomplete) {
+                if (n.isDeleted() || n.incomplete) {
                     continue;
                 }
@@ -451,10 +454,10 @@
             return null;
         for (Way w : ds.ways) {
-            if (w.deleted || w.incomplete) {
+            if (w.isDeleted() || w.incomplete) {
                 continue;
             }
             Node lastN = null;
             for (Node n : w.getNodes()) {
-                if (n.deleted || n.incomplete) {
+                if (n.isDeleted() || n.incomplete) {
                     continue;
                 }
@@ -477,5 +480,5 @@
         }
         for (Node n : ds.nodes) {
-            if (!n.deleted && !n.incomplete
+            if (!n.isDeleted() && !n.incomplete
                     && getPoint(n).distanceSq(p) < snapDistance) {
                 nearest.add(n);
@@ -499,5 +502,5 @@
             return null;
         for (Node n : ds.nodes) {
-            if (!n.deleted && !n.incomplete
+            if (!n.isDeleted() && !n.incomplete
                     && getPoint(n).distanceSq(p) < snapDistance) {
                 nearest.add(n);
Index: trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java	(revision 2025)
@@ -45,10 +45,10 @@
      */
     public PleaseWaitRunnable(String title, boolean ignoreException) {
-        this(title, new PleaseWaitProgressMonitor(), ignoreException);
+        this(title, new PleaseWaitProgressMonitor(title), ignoreException);
     }
 
     public PleaseWaitRunnable(String title, ProgressMonitor progressMonitor, boolean ignoreException) {
         this.title = title;
-        this.progressMonitor = progressMonitor == null?new PleaseWaitProgressMonitor():progressMonitor;
+        this.progressMonitor = progressMonitor == null?new PleaseWaitProgressMonitor(title):progressMonitor;
         this.ignoreException = ignoreException;
         this.progressMonitor.addCancelListener(this);
Index: trunk/src/org/openstreetmap/josm/gui/SelectionManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/SelectionManager.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/SelectionManager.java	(revision 2025)
@@ -286,5 +286,5 @@
             // nodes
             for (Node n : nc.getCurrentDataSet().nodes) {
-                if (!n.deleted && !n.incomplete && r.contains(nc.getPoint(n))) {
+                if (!n.isDeleted() && !n.incomplete && r.contains(nc.getPoint(n))) {
                     selection.add(n);
                 }
@@ -293,5 +293,5 @@
             // ways
             for (Way w : nc.getCurrentDataSet().ways) {
-                if (w.deleted || w.getNodesCount() == 0 || w.incomplete) {
+                if (w.isDeleted() || w.getNodesCount() == 0 || w.incomplete) {
                     continue;
                 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 2025)
@@ -47,4 +47,5 @@
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.io.SaveLayersDialog;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
@@ -299,113 +300,42 @@
         }
 
-        protected boolean confirmSkipSaving(OsmDataLayer layer) {
-            int result = new ExtendedDialog(Main.parent,
-                    tr("Unsaved Changes"),
-                    tr("There are unsaved changes in the layer''{0}''. Delete the layer anwyay?",layer.getName()),
-                    new String[] {tr("Delete Layer"), tr("Cancel")},
-                    new String[] {"dialogs/delete.png", "cancel.png"}).getValue();
-
-            return result == 1;
-        }
-
-        protected boolean confirmDeleteLayer(Layer layer) {
-            return ConditionalOptionPaneUtil.showConfirmationDialog(
-                    "delete_layer",
-                    Main.parent,
-                    tr("Do you really want to delete layer ''{0}''?", layer.getName()),
-                    tr("Confirmation"),
-                    JOptionPane.YES_NO_OPTION,
-                    JOptionPane.QUESTION_MESSAGE,
-                    JOptionPane.YES_OPTION);
-        }
-
-        protected DeleteDecision confirmDeleteMultipleLayer(Layer layer, int idx, int numLayers) {
-            String options[] = new String[] {
-                    tr("Yes"),
-                    tr("No"),
-                    tr("Delete all"),
-                    tr("Cancel")
-            };
-            int ret = ConditionalOptionPaneUtil.showOptionDialog(
-                    "delete_layer",
-                    Main.parent,
-                    tr("Do you really want to delete layer ''{0}''?", layer.getName()),
-                    tr("Deleting layer {0} of {1}", idx+1, numLayers),
-                    JOptionPane.DEFAULT_OPTION,
-                    JOptionPane.QUESTION_MESSAGE,
-                    options,
-                    options[0]
-            );
-            switch(ret) {
-                case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: return DeleteDecision.deleteAll;
-                case JOptionPane.CLOSED_OPTION: return DeleteDecision.cancel;
-                case 0: return DeleteDecision.deleteCurrent;
-                case 1: return DeleteDecision.dontDeleteCurrent;
-                case 2: return DeleteDecision.deleteAll;
-                case 3: return DeleteDecision.cancel;
-                default:
-                    // shouldn't happen. This is the safest option.
-                    return DeleteDecision.cancel;
-            }
-        }
-
-
-        public void deleteSingleLayer(Layer layer) {
-            if (layer == null)
+        protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) {
+            SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
+            List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
+            for (Layer l: selectedLayers) {
+                if (! (l instanceof OsmDataLayer)) {
+                    continue;
+                }
+                OsmDataLayer odl = (OsmDataLayer)l;
+                if (odl.requiresSaveToFile() || odl.requiresUploadToServer()) {
+                    layersWithUnmodifiedChanges.add(odl);
+                }
+            }
+            dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
+            if (!layersWithUnmodifiedChanges.isEmpty()) {
+                dialog.getModel().populate(layersWithUnmodifiedChanges);
+                dialog.setVisible(true);
+                switch(dialog.getUserAction()) {
+                    case CANCEL: return false;
+                    case PROCEED: return true;
+                    default: return false;
+                }
+            }
+            return true;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            List<Layer> selectedLayers;
+            if (this.layer == null) {
+                selectedLayers = getModel().getSelectedLayers();
+            } else {
+                selectedLayers = Collections.singletonList(this.layer);
+            }
+            if (selectedLayers.isEmpty())
                 return;
-            if (layer instanceof OsmDataLayer) {
-                OsmDataLayer dataLayer = (OsmDataLayer)layer;
-                if (dataLayer.isModified()) {
-                    if (! confirmSkipSaving(dataLayer))
-                        return;
-                }
-                else if (!confirmDeleteLayer(dataLayer))
-                    return;
-            } else {
-                if (!confirmDeleteLayer(layer))
-                    return;
-            }
-            // model and view are going to be updated via LayerChangeListener
-            //
-            Main.main.removeLayer(layer);
-        }
-
-        public void deleteMultipleLayers(List<Layer> layers) {
-            boolean doAskConfirmation = true;
-            for (int i=0; i < layers.size(); i++) {
-                Layer layer = layers.get(i);
-                if (layer instanceof OsmDataLayer) {
-                    OsmDataLayer dataLayer = (OsmDataLayer)layer;
-                    if (dataLayer.isModified() && ! confirmSkipSaving(dataLayer)) {
-                        continue;
-                    }
-                }
-                if (doAskConfirmation) {
-                    DeleteDecision decision = confirmDeleteMultipleLayer(layer, i, layers.size());
-                    switch(decision) {
-                        case deleteCurrent: /* do nothing */ break;
-                        case deleteAll: doAskConfirmation = false; break;
-                        case dontDeleteCurrent: continue;
-                        case cancel: return;
-                    }
-                }
-                // model and view are going to be updated via LayerChangeListener
-                //
-                Main.main.removeLayer(layer);
-            }
-        }
-
-        public void actionPerformed(ActionEvent e) {
-            if (this.layer == null) {
-                List<Layer> selectedLayers = getModel().getSelectedLayers();
-                if (selectedLayers.isEmpty())
-                    return;
-                if (selectedLayers.size() == 1) {
-                    deleteSingleLayer(selectedLayers.get(0));
-                } else {
-                    deleteMultipleLayers(selectedLayers);
-                }
-            } else {
-                deleteSingleLayer(this.layer);
+            if (! enforceUploadOrSaveModifiedData(selectedLayers))
+                return;
+            for(Layer l: selectedLayers) {
+                Main.main.removeLayer(l);
             }
         }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 2025)
@@ -135,5 +135,5 @@
             int i = 0;
             for (OsmPrimitive e : DataSet.sort(Main.main.getCurrentDataSet().relations)) {
-                if (!e.deleted && !e.incomplete) {
+                if (!e.isDeleted() && !e.incomplete) {
                     list.setElementAt(e, i++);
                 }
@@ -381,6 +381,8 @@
             Relation copy = new Relation();
             copy.cloneFrom(original);
+            // FIXME: id is going to be hidden. How to reset a primitive?
+            //
             copy.id = 0;
-            copy.modified = true;
+            copy.setModified(true);
             RelationEditor editor = RelationEditor.getEditor(
                     Main.main.getEditLayer(),
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ReferringRelationsBrowser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ReferringRelationsBrowser.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ReferringRelationsBrowser.java	(revision 2025)
@@ -107,5 +107,5 @@
                     getLayer(),
                     full,
-                    new PleaseWaitProgressMonitor()
+                    new PleaseWaitProgressMonitor(tr("Loading parent relations"))
             );
             task.setContinuation(
Index: trunk/src/org/openstreetmap/josm/gui/io/AbstractIOTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/AbstractIOTask.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/AbstractIOTask.java	(revision 2025)
@@ -0,0 +1,96 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+/**
+ * This is an abstract task for uploading or saving a data layer.
+ * 
+ */
+public abstract class AbstractIOTask implements Runnable {
+
+    /** indicates whether the task has been cancelled */
+    private boolean cancelled;
+    /** indicates whether the task has been failed */
+    private boolean failed;
+    /** the last exception caught */
+    private Exception lastException;
+
+    public AbstractIOTask() {
+        cancelled = false;
+        failed = false;
+        lastException = null;
+    }
+
+    /**
+     * Replies true if the task has been cancelled
+     * 
+     * @return true if the task has been cancelled
+     */
+    public boolean isCancelled() {
+        return cancelled;
+    }
+
+    /**
+     * Set whether this task has been cancelled
+     * 
+     * @param cancelled true, if the task has been cancelled; false otherwise
+     */
+    protected void setCancelled(boolean cancelled) {
+        this.cancelled = cancelled;
+    }
+
+    /**
+     * Replies true if the task has been failed
+     * 
+     * @return true if the task has been failed
+     */
+    public boolean isFailed() {
+        return failed || lastException != null;
+    }
+
+    /**
+     * Sets whether the task has been failed
+     * 
+     * @param failed whether the task has been failed
+     */
+    protected void setFailed(boolean failed) {
+        this.failed = failed;
+    }
+
+    /**
+     * Replies the last exception caught
+     * 
+     * @return the last exception caught; null, if no exception was caught
+     */
+    public Exception getLastException() {
+        return lastException;
+    }
+
+    /**
+     * Sets the last exception caught
+     * 
+     * @param lastException the last exception
+     */
+    protected void setLastException(Exception lastException) {
+        this.lastException = lastException;
+    }
+
+    /**
+     * Replies true if this  task was successful, i.e. if it wasn't
+     * cancelled and didn't fail
+     * 
+     * @return true if this  task was successful
+     */
+    public boolean isSuccessful() {
+        return !isCancelled() && !isFailed();
+    }
+
+    /**
+     * Runs the task
+     */
+    public abstract void run();
+
+    /**
+     * Cancel the task
+     */
+    public abstract void cancel();
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/FilenameCellEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/FilenameCellEditor.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/FilenameCellEditor.java	(revision 2025)
@@ -0,0 +1,155 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.io.File;
+import java.util.EventObject;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.event.CellEditorListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.table.TableCellEditor;
+
+import org.openstreetmap.josm.actions.SaveActionBase;
+
+/**
+ * This is a {@see TableCellEditor} for filenames. It provides a text input field and
+ * a button for launchinig a {@see JFileChooser}.
+ * 
+ *
+ */
+class FilenameCellEditor extends JPanel implements TableCellEditor {
+    private JTextField tfFileName;
+    private CopyOnWriteArrayList<CellEditorListener> listeners;
+    private File value;
+
+    /**
+     * build the GUI
+     */
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        add(tfFileName = new JTextField(), gc);
+
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 0.0;
+        gc.weighty = 1.0;
+        add(new JButton(new LaunchFileChooserAction()));
+
+        tfFileName.addFocusListener(
+                new FocusAdapter() {
+                    @Override
+                    public void focusGained(FocusEvent e) {
+                        tfFileName.selectAll();
+                    }
+                }
+        );
+    }
+
+    public FilenameCellEditor() {
+        listeners = new CopyOnWriteArrayList<CellEditorListener>();
+        build();
+    }
+
+
+    public void addCellEditorListener(CellEditorListener l) {
+        if (!listeners.contains(l)) {
+            listeners.add(l);
+        }
+    }
+
+    protected void fireEditingCanceled() {
+        for (CellEditorListener l: listeners) {
+            l.editingCanceled(new ChangeEvent(this));
+        }
+    }
+
+    protected void fireEditingStopped() {
+        for (CellEditorListener l: listeners) {
+            l.editingStopped(new ChangeEvent(this));
+        }
+    }
+
+    public void cancelCellEditing() {
+        fireEditingCanceled();
+    }
+
+    public Object getCellEditorValue() {
+        return value;
+    }
+
+    public boolean isCellEditable(EventObject anEvent) {
+        return true;
+    }
+
+    public void removeCellEditorListener(CellEditorListener l) {
+        if (listeners.contains(l)) {
+            listeners.remove(l);
+        }
+    }
+
+    public boolean shouldSelectCell(EventObject anEvent) {
+        return true;
+    }
+
+    public boolean stopCellEditing() {
+        if (tfFileName.getText() == null || tfFileName.getText().trim().equals("")) {
+            value = null;
+        } else {
+            value = new File(tfFileName.getText());
+        }
+        fireEditingStopped();
+        return true;
+    }
+
+    public void setInitialValue(File initialValue) {
+        this.value = initialValue;
+        if (initialValue == null) {
+            this.tfFileName.setText("");
+        } else {
+            this.tfFileName.setText(initialValue.toString());
+        }
+    }
+
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+        SaveLayerInfo info = (SaveLayerInfo)value;
+        setInitialValue(info.getFile());
+        tfFileName.selectAll();
+        return this;
+    }
+
+    class LaunchFileChooserAction extends AbstractAction {
+        public LaunchFileChooserAction() {
+            putValue(NAME, "...");
+            putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            File f = SaveActionBase.createAndOpenSaveFileChooser(tr("Select filename"), "osm");
+            if (f != null) {
+                FilenameCellEditor.this.tfFileName.setText(f.toString());
+                FilenameCellEditor.this.tfFileName.selectAll();
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/FlagCellEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/FlagCellEditor.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/FlagCellEditor.java	(revision 2025)
@@ -0,0 +1,97 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import java.awt.Component;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.EventObject;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.JCheckBox;
+import javax.swing.JTable;
+import javax.swing.event.CellEditorListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.table.TableCellEditor;
+
+/**
+ * This is {@see TableCellEditor} for a boolean flag. It is used in two table columns of
+ * {@see SaveLayersTable} and renders the values of {@see SaveLayerInfo#isDoSaveToFile()}
+ * and {@see SaveLayerInfo#isDoUploadToServer()}
+ * 
+ */
+class SaveFlagCellEditor extends JCheckBox implements TableCellEditor {
+    private CopyOnWriteArrayList<CellEditorListener> listeners;
+    private boolean value;
+
+    public SaveFlagCellEditor() {
+        listeners = new CopyOnWriteArrayList<CellEditorListener>();
+        addMouseListener(
+                new MouseAdapter() {
+                    @Override
+                    public void mouseExited(MouseEvent e) {
+                        stopCellEditing();
+                    }
+                }
+        );
+    }
+
+    public void addCellEditorListener(CellEditorListener l) {
+        if (!listeners.contains(l)) {
+            listeners.add(l);
+        }
+    }
+
+    protected void fireEditingCanceled() {
+        for (CellEditorListener l: listeners) {
+            l.editingCanceled(new ChangeEvent(this));
+        }
+    }
+
+    protected void fireEditingStopped() {
+        for (CellEditorListener l: listeners) {
+            l.editingStopped(new ChangeEvent(this));
+        }
+    }
+
+    public void cancelCellEditing() {
+        fireEditingCanceled();
+    }
+
+    public Object getCellEditorValue() {
+        return value;
+    }
+
+    public boolean isCellEditable(EventObject anEvent) {
+        return true;
+    }
+
+    public void removeCellEditorListener(CellEditorListener l) {
+        if (listeners.contains(l)) {
+            listeners.remove(l);
+        }
+    }
+
+    public boolean shouldSelectCell(EventObject anEvent) {
+        return true;
+    }
+
+    public void setInitialValue(boolean value) {
+        this.value = value;
+        setSelected(value);
+    }
+
+    public boolean stopCellEditing() {
+        this.value = isSelected();
+        fireEditingStopped();
+        return true;
+    }
+
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+        SaveLayerInfo info = (SaveLayerInfo)value;
+        switch(column) {
+            case 4: setInitialValue(info.isDoUploadToServer()); break;
+            case 5: setInitialValue(info.isDoSaveToFile()); break;
+        }
+        return this;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayerInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayerInfo.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayerInfo.java	(revision 2025)
@@ -0,0 +1,200 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import java.io.File;
+
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * SaveLayerInfo represents the information, user preferences and save/upload states of
+ * a layer which might be uploaded/saved.
+ * 
+ */
+class SaveLayerInfo implements Comparable<SaveLayerInfo> {
+
+    /** the osm data layer */
+    private OsmDataLayer layer;
+    private boolean doSaveToFile;
+    private boolean doUploadToServer;
+    private File file;
+    private UploadOrSaveState uploadState;
+    private UploadOrSaveState saveState;
+
+    /**
+     * 
+     * @param layer the layer. Must not be null.
+     * @throws IllegalArgumentException thrown if layer is null
+     */
+    public SaveLayerInfo(OsmDataLayer layer) {
+        if (layer == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "layer"));
+        this.layer = layer;
+        this.doSaveToFile = layer.requiresSaveToFile();
+        this.doUploadToServer = layer.requiresUploadToServer();
+        this.file = layer.getAssociatedFile();
+    }
+
+    /**
+     * Replies the layer this info objects holds information for
+     * 
+     * @return the layer this info objects holds information for
+     */
+    public OsmDataLayer getLayer() {
+        return layer;
+    }
+
+    /**
+     * Replies true if this layer should be saved to a file; false, otherwise
+     * 
+     * @return true if this layers should be saved to a file; false, otherwise
+     */
+    public boolean isDoSaveToFile() {
+        return doSaveToFile;
+    }
+
+    /**
+     * Sets whether this layer should be saved to a file
+     * 
+     * @param doSaveToFile true to save; false, to skip saving
+     */
+    public void setDoSaveToFile(boolean doSaveToFile) {
+        this.doSaveToFile = doSaveToFile;
+    }
+
+    /**
+     * Replies true if this layer should be uploaded to the server; false, otherwise
+     * 
+     * @return true if this layer should be uploaded to the server; false, otherwise
+     */
+    public boolean isDoUploadToServer() {
+        return doUploadToServer;
+    }
+
+    /**
+     * Sets whether this layer should be uploaded to a file
+     * 
+     * @param doSaveToFile true to upload; false, to skip uploading
+     */
+
+    public void setDoUploadToServer(boolean doUploadToServer) {
+        this.doUploadToServer = doUploadToServer;
+    }
+
+    /**
+     * Replies true if this layer should be uploaded to the server and saved to file.
+     * 
+     * @return true if this layer should be uploaded to the server and saved to file
+     */
+    public boolean isDoSaveAndUpload() {
+        return isDoSaveToFile() && isDoUploadToServer();
+    }
+
+    /**
+     * Replies the name of the layer
+     * 
+     * @return the name of the layer
+     */
+    public String getName() {
+        return layer.getName() == null ? "" : layer.getName();
+    }
+
+    /**
+     * Replies the file this layer should be saved to, if {@see #isDoSaveToFile()} is true
+     * 
+     * @return the file this layer should be saved to, if {@see #isDoSaveToFile()} is true
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * Sets the file this layer should be saved to, if {@see #isDoSaveToFile()} is true
+     * 
+     * @param file the file
+     */
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    public int compareTo(SaveLayerInfo o) {
+        if (isDoSaveAndUpload()) {
+            if (o.isDoSaveAndUpload())
+                return getName().compareTo(o.getName());
+            return -1;
+        } else if (o.isDoSaveAndUpload())
+            return 1;
+        if (isDoUploadToServer()) {
+            if (o.isDoUploadToServer())
+                return getName().compareTo(o.getName());
+            return -1;
+        } else if (o.isDoUploadToServer())
+            return 1;
+        if (isDoSaveToFile()) {
+            if (o.isDoSaveToFile())
+                return getName().compareTo(o.getName());
+            return -1;
+        } else if (o.isDoSaveToFile())
+            return 1;
+        return getName().compareTo(o.getName());
+    }
+
+    /**
+     * Replies the upload state of {@see #getLayer()}.
+     * <ul>
+     *   <li>{@see UploadOrSaveState#OK} if {@see #getLayer() was successfully uploaded</li>
+     *   <li>{@see UploadOrSaveState#CANCELLED} if uploading {@see #getLayer() was cancelled</li>
+     *   <li>{@see UploadOrSaveState#FAILED} if uploading {@see #getLayer() has failed</li>
+     * </ul>
+     * 
+     * @return the upload state
+     */
+    public UploadOrSaveState getUploadState() {
+        return uploadState;
+    }
+
+    /**
+     * Sets the upload state for {@see #getLayer()}
+     * 
+     * @param uploadState the upload state
+     */
+    public void setUploadState(UploadOrSaveState uploadState) {
+        this.uploadState = uploadState;
+    }
+
+    /**
+     * Replies the save state of {@see #getLayer()}.
+     * <ul>
+     *   <li>{@see UploadOrSaveState#OK} if {@see #getLayer() was successfully saved to file</li>
+     *   <li>{@see UploadOrSaveState#CANCELLED} if saving {@see #getLayer() was cancelled</li>
+     *   <li>{@see UploadOrSaveState#FAILED} if saving {@see #getLayer() has failed</li>
+     * </ul>
+     * 
+     * @return the save state
+     */
+    public UploadOrSaveState getSaveState() {
+        return saveState;
+    }
+
+    /**
+     * Sets the save state for {@see #getLayer()}
+     * 
+     * @param saveState save the upload state
+     */
+    public void setSaveState(UploadOrSaveState saveState) {
+        this.saveState = saveState;
+    }
+
+    /**
+     * Resets the upload and save state
+     * 
+     * @see #setUploadState(UploadOrSaveState)
+     * @see #setSaveState(UploadOrSaveState)
+     * @see #getUploadState()
+     * @see #getSaveState()
+     */
+    public void resetUploadAndSaveState() {
+        this.uploadState = null;
+        this.saveState = null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayerInfoCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayerInfoCellRenderer.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayerInfoCellRenderer.java	(revision 2025)
@@ -0,0 +1,148 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.io.OsmApi;
+
+/**
+ * This is a {@see TableCellRenderer} for rendering the various fields of a
+ * {@see SaveLayerInfo} in the table {@see SaveLayersTable}.
+ * 
+ *
+ */
+class SaveLayerInfoCellRenderer implements TableCellRenderer {
+    private JLabel lblRenderer;
+    private JCheckBox cbRenderer;
+
+    public SaveLayerInfoCellRenderer() {
+        lblRenderer = new JLabel();
+        cbRenderer = new JCheckBox();
+    }
+
+    protected Component prepareLayerNameRenderer(SaveLayerInfo info) {
+        lblRenderer.setIcon(info.getLayer().getIcon());
+        lblRenderer.setText(info.getName());
+        lblRenderer.setToolTipText(info.getLayer().getToolTipText());
+        return lblRenderer;
+    }
+
+    protected Component prepareUploadRequiredRenderer(SaveLayerInfo info) {
+        lblRenderer.setIcon(null);
+        lblRenderer.setHorizontalAlignment(JLabel.CENTER);
+        String text = info.getLayer().requiresUploadToServer() ? tr("Yes") : tr("No");
+        lblRenderer.setText(text);
+        if (info.getLayer().requiresUploadToServer()) {
+            lblRenderer.setToolTipText(tr("Layer ''{0}'' has modifications which should be uploaded to the server.", info.getName()));
+        } else {
+            lblRenderer.setToolTipText(tr("Layer ''{0}'' has no modifications to be uploaded", info.getName()));
+        }
+        return lblRenderer;
+    }
+
+    protected Component prepareSaveToFileRequired(SaveLayerInfo info) {
+        lblRenderer.setIcon(null);
+        lblRenderer.setHorizontalAlignment(JLabel.CENTER);
+        String text = info.getLayer().requiresSaveToFile() ? tr("Yes") : tr("No");
+        lblRenderer.setText(text);
+        if (info.getLayer().requiresSaveToFile()) {
+            lblRenderer.setToolTipText(tr("Layer ''{0}'' has modifications which should be saved to its assoicated file ''{1}''.", info.getName(), info.getFile().toString()));
+        } else {
+            lblRenderer.setToolTipText(tr("Layer ''{0}'' has no modifications to be saved", info.getName()));
+        }
+        return lblRenderer;
+    }
+
+    protected boolean canWrite(File f) {
+        if (f == null) return false;
+        if (f.isDirectory()) return false;
+        if (f.exists() && f.canWrite()) return true;
+        if (!f.exists() && f.getParentFile() != null && f.getParentFile().canWrite())
+            return true;
+        return false;
+    }
+
+    protected Component prepareFileNameRenderer(SaveLayerInfo info) {
+        lblRenderer.setIcon(null);
+        if (info.getFile() == null) {
+            if (!info.isDoSaveToFile()) {
+                lblRenderer.setText(tr("No file associated with this layer"));
+            } else {
+                lblRenderer.setBackground(new Color(255,197,197));
+                lblRenderer.setText(tr("Please select a file"));
+            }
+            lblRenderer.setFont(lblRenderer.getFont().deriveFont(Font.ITALIC));
+            lblRenderer.setToolTipText(tr("Layer ''{0}'' is not backed by a file", info.getName()));
+        } else {
+            String text = info.getFile().getName();
+            String parent = info.getFile().getParent();
+            if (parent != null) {
+                if (parent.length() <= 10) {
+                    text = info.getFile().getPath();
+                } else {
+                    text = parent.substring(0, 10) + "..." + File.separator + text;
+                }
+            }
+            lblRenderer.setText(text);
+            lblRenderer.setToolTipText(info.getFile().getAbsolutePath());
+            if (info.isDoSaveToFile() && !canWrite(info.getFile())) {
+                lblRenderer.setBackground(new Color(255,197,197));
+                lblRenderer.setToolTipText(tr("File ''{0}'' is not writable. Please enter another file name.", info.getFile().getPath()));
+            }
+        }
+        return lblRenderer;
+    }
+
+    protected Component prepareUploadRenderer(SaveLayerInfo info){
+        cbRenderer.setSelected(info.isDoUploadToServer());
+        lblRenderer.setToolTipText(tr("Select to upload layer ''{0}'' to the server ''{1}''", info.getName(), OsmApi.getOsmApi().getBaseUrl()));
+        return cbRenderer;
+    }
+
+    protected Component prepareSaveToFileRenderer(SaveLayerInfo info){
+        cbRenderer.setSelected(info.isDoSaveToFile());
+        lblRenderer.setToolTipText(tr("Select to upload layer ''{0}'' to the server ''{1}''", info.getName(), OsmApi.getOsmApi().getBaseUrl()));
+        return cbRenderer;
+    }
+
+    protected void resetRenderers() {
+        lblRenderer.setOpaque(true);
+        lblRenderer.setBackground(UIManager.getColor("Table.background"));
+        lblRenderer.setIcon(null);
+        lblRenderer.setText("");
+        lblRenderer.setFont(UIManager.getFont("Table.font"));
+        lblRenderer.setHorizontalAlignment(JLabel.LEFT);
+
+        cbRenderer.setSelected(false);
+        cbRenderer.setOpaque(true);
+        cbRenderer.setBackground(UIManager.getColor("Table.background"));
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+            boolean hasFocus, int row, int column) {
+
+        resetRenderers();
+        SaveLayerInfo info = (SaveLayerInfo)value;
+        switch(column) {
+            case 0: return prepareLayerNameRenderer(info);
+            case 1: return prepareUploadRequiredRenderer(info);
+            case 2: return prepareSaveToFileRequired(info);
+            case 3: return prepareFileNameRenderer(info);
+            case 4: return prepareUploadRenderer(info);
+            case 5: return prepareSaveToFileRenderer(info);
+        }
+        return null;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayerTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayerTask.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayerTask.java	(revision 2025)
@@ -0,0 +1,68 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.actions.SaveAction;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+
+/**
+ * SaveLayerTask saves the data managed by an {@see OsmDataLayer} to the
+ * {@see OsmDataLayer#getAssociatedFile()}.
+ * 
+ * <pre>
+ *     ExecutorService executorService = ...
+ *     SaveLayerTask task = new SaveLayerTask(layer, monitor);
+ *     Future<?> taskFuture = executorServce.submit(task)
+ *     try {
+ *        // wait for the task to complete
+ *        taskFuture.get();
+ *     } catch(Exception e) {
+ *        e.printStackTracek();
+ *     }
+ * </pre>
+ */
+class SaveLayerTask extends AbstractIOTask {
+    private SaveLayerInfo layerInfo;
+    private ProgressMonitor parentMonitor;
+
+    /**
+     * 
+     * @param layerInfo information about the layer to be saved to save. Must not be null.
+     * @param monitor the monitor. Set to {@see NullProgressMonitor#INSTANCE} if null
+     * @throws IllegalArgumentException thrown if layer is null
+     */
+    protected SaveLayerTask(SaveLayerInfo layerInfo, ProgressMonitor monitor) {
+        if (layerInfo == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "layerInfo"));
+        if (monitor == null) {
+            monitor = NullProgressMonitor.INSTANCE;
+        }
+        this.layerInfo =  layerInfo;
+        this.parentMonitor = monitor;
+    }
+
+    @Override
+    public void run() {
+        try {
+            parentMonitor.subTask(tr("Saving layer to ''{0}'' ...", layerInfo.getFile().toString()));
+            layerInfo.getLayer().setAssociatedFile(layerInfo.getFile());
+            if (!new SaveAction().doSave(layerInfo.getLayer())) {
+                setFailed(true);
+                return;
+            }
+            if (!isCancelled()) {
+                layerInfo.getLayer().onPostSaveToFile();
+            }
+        } catch(Exception e) {
+            e.printStackTrace();
+            setLastException(e);
+        }
+    }
+
+    @Override
+    public void cancel() {
+        setCancelled(true);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayersColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayersColumnModel.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayersColumnModel.java	(revision 2025)
@@ -0,0 +1,75 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+
+
+class SaveLayersTableColumnModel extends DefaultTableColumnModel {
+    protected void build() {
+        TableColumn col = null;
+        TableCellRenderer renderer = new SaveLayerInfoCellRenderer();
+        TableCellEditor fileNameEditor = new FilenameCellEditor();
+        TableCellEditor saveFlagEditor = new SaveFlagCellEditor();
+
+        // column 0 - Layer
+        col = new TableColumn(0);
+        col.setHeaderValue(tr("Layer"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setPreferredWidth(100);
+        addColumn(col);
+
+        // column 1 - Upload required
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("Should upload?"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setPreferredWidth(50);
+        addColumn(col);
+
+        // column 2 - Save to file required
+        col = new TableColumn(2);
+        col.setHeaderValue(tr("Should save?"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setPreferredWidth(50);
+        addColumn(col);
+
+        // column 3 - filename
+        col = new TableColumn(3);
+        col.setHeaderValue(tr("Filename"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setCellEditor(fileNameEditor);
+        col.setPreferredWidth(200);
+        addColumn(col);
+
+        // column 4 - Upload
+        col = new TableColumn(4);
+        col.setHeaderValue(tr("Upload"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setCellEditor(saveFlagEditor);
+        col.setPreferredWidth(30);
+        addColumn(col);
+
+        // column 5 - Save
+        col = new TableColumn(5);
+        col.setHeaderValue(tr("Save"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setCellEditor(saveFlagEditor);
+        col.setPreferredWidth(30);
+
+        addColumn(col);
+    }
+
+    public SaveLayersTableColumnModel() {
+        build();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 2025)
@@ -0,0 +1,522 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.progress.SwingRenderingProgressMonitor;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+public class SaveLayersDialog extends JDialog {
+    static public enum UserAction {
+        /**
+         * save/upload layers was successful, proceed with operation
+         */
+        PROCEED,
+        /**
+         * save/upload of layers was not successful or user cancelled
+         * operation
+         */
+        CANCEL
+    }
+
+    private SaveLayersModel model;
+    private UserAction action = UserAction.CANCEL;
+    private UploadAndSaveProgressRenderer pnlUploadLayers;
+
+    private SaveAndProceedAction saveAndProceedAction;
+    private DiscardAndProceedAction discardAndProceedAction;
+    private CancelAction cancelAction;
+    private SaveAndUploadTask saveAndUploadTask;
+
+    /**
+     * builds the GUI
+     */
+    protected void build() {
+        WindowGeometry geometry = WindowGeometry.centerOnScreen(new Dimension(600,300));
+        geometry.apply(this);
+        getContentPane().setLayout(new BorderLayout());
+
+        model = new SaveLayersModel();
+        SaveLayersTable table;
+        JScrollPane pane = new JScrollPane(table = new SaveLayersTable(model));
+        model.addPropertyChangeListener(table);
+        getContentPane().add(pane, BorderLayout.CENTER);
+        getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
+        table.getTableHeader().setPreferredSize(new Dimension(table.getTableHeader().getWidth(), 40));
+
+        addWindowListener(new WindowClosingAdapter());
+    }
+
+    /**
+     * builds the button row
+     * 
+     * @return the panel with the button row
+     */
+    protected JPanel buildButtonRow() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
+
+        saveAndProceedAction = new SaveAndProceedAction();
+        model.addPropertyChangeListener(saveAndProceedAction);
+        pnl.add(new SideButton(saveAndProceedAction));
+
+        discardAndProceedAction = new DiscardAndProceedAction();
+        model.addPropertyChangeListener(discardAndProceedAction);
+        pnl.add(new SideButton(discardAndProceedAction));
+
+        cancelAction = new CancelAction();
+        pnl.add(new SideButton(cancelAction));
+
+        JPanel pnl2 = new JPanel();
+        pnl2.setLayout(new BorderLayout());
+        pnl2.add(pnlUploadLayers = new UploadAndSaveProgressRenderer(), BorderLayout.CENTER);
+        model.addPropertyChangeListener(pnlUploadLayers);
+        pnl2.add(pnl, BorderLayout.SOUTH);
+        return pnl2;
+    }
+
+    public void prepareForSavingAndUpdatingLayersBeforeExit() {
+        setTitle(tr("Unsaved changes - Save/Upload before exiting?"));
+        this.saveAndProceedAction.initForSaveAndExit();
+        this.discardAndProceedAction.initForDiscardAndExit();
+    }
+
+    public void prepareForSavingAndUpdatingLayersBeforeDelete() {
+        setTitle(tr("Unsaved changes - Save/Upload before deleting?"));
+        this.saveAndProceedAction.initForSaveAndDelete();
+        this.discardAndProceedAction.initForDiscardAndDelete();
+    }
+
+    public SaveLayersDialog(Component parent) {
+        super(JOptionPane.getFrameForComponent(parent), true /* modal */);
+        build();
+    }
+
+    public UserAction getUserAction() {
+        return this.action;
+    }
+
+    public SaveLayersModel getModel() {
+        return model;
+    }
+
+    protected void launchSafeAndUploadTask() {
+        ProgressMonitor monitor = new SwingRenderingProgressMonitor(pnlUploadLayers);
+        monitor.beginTask(tr("Uploading and saving modified layers ..."));
+        this.saveAndUploadTask = new SaveAndUploadTask(model, monitor);
+        new Thread(saveAndUploadTask).start();
+    }
+
+    protected void cancelSafeAndUploadTask() {
+        if (this.saveAndUploadTask != null) {
+            this.saveAndUploadTask.cancel();
+        }
+        model.setMode(Mode.EDITING_DATA);
+    }
+
+    private static class  LayerListWarningMessagePanel extends JPanel {
+        private JLabel lblMessage;
+        private JList lstLayers;
+
+        protected void build() {
+            setLayout(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.gridx = 0;
+            gc.gridy = 0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            gc.weighty = 0.0;
+            add(lblMessage = new JLabel(), gc);
+            lblMessage.setHorizontalAlignment(JLabel.LEFT);
+            lstLayers = new JList();
+            lstLayers.setCellRenderer(
+                    new DefaultListCellRenderer() {
+                        @Override
+                        public Component getListCellRendererComponent(JList list, Object value, int index,
+                                boolean isSelected, boolean cellHasFocus) {
+                            SaveLayerInfo info = (SaveLayerInfo)value;
+                            setIcon(info.getLayer().getIcon());
+                            setText(info.getName());
+                            return this;
+                        }
+                    }
+            );
+            gc.gridx = 0;
+            gc.gridy = 1;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weightx = 1.0;
+            gc.weighty = 1.0;
+            add(lstLayers,gc);
+        }
+
+        public LayerListWarningMessagePanel(String msg, List<SaveLayerInfo> infos) {
+            build();
+            lblMessage.setText(msg);
+            lstLayers.setListData(infos.toArray());
+        }
+    }
+
+    protected void warnLayersWithConflictsAndUploadRequest(List<SaveLayerInfo> infos) {
+        String msg = trn("<html>{0} layer has unresolved conflicts.<br>"
+                + "Either resolve them first or discard the modifications.<br>"
+                + "Layer with conflicts:</html>",
+                "<html>{0} layers have unresolved conflicts.<br>"
+                + "Either resolve them first or discard the modifications.<br>"
+                + "Layers with conflicts:</html>",
+                infos.size(),
+                infos.size());
+        JOptionPane.showConfirmDialog(
+                Main.parent,
+                new LayerListWarningMessagePanel(msg, infos),
+                tr("Unsafed data and conflicts"),
+                JOptionPane.DEFAULT_OPTION,
+                JOptionPane.WARNING_MESSAGE
+        );
+    }
+
+    protected void warnLayersWithoutFilesAndSaveRequest(List<SaveLayerInfo> infos) {
+        String msg = trn("<html>{0} layer needs saving but has no associated file.<br>"
+                + "Either select a file for this layer or discard the changes.<br>"
+                + "Layer without a file:</html>",
+                "<html>{0} layers need saving but have no associated file.<br>"
+                + "Either select a file for each of them or discard the changes.<br>"
+                + "Layers without a file:</html>",
+                infos.size(),
+                infos.size());
+        JOptionPane.showConfirmDialog(
+                Main.parent,
+                new LayerListWarningMessagePanel(msg, infos),
+                tr("Unsafed data and missing associated file"),
+                JOptionPane.DEFAULT_OPTION,
+                JOptionPane.WARNING_MESSAGE
+        );
+    }
+
+    protected void warnLayersWithIllegalFilesAndSaveRequest(List<SaveLayerInfo> infos) {
+        String msg = trn("<html>{0} layer needs saving but has an associated file<br>"
+                + "which can't be written.<br>"
+                + "Either select another file for this layer or discard the changes.<br>"
+                + "Layer with a non-writable file:</html>",
+                "<html>{0} layers need saving but have associated files<br>"
+                + "which can't be written.<br>"
+                + "Either select another file for each of them or discard the changes.<br>"
+                + "Layers with non-writable files:</html>",
+                infos.size(),
+                infos.size());
+        JOptionPane.showConfirmDialog(
+                Main.parent,
+                new LayerListWarningMessagePanel(msg, infos),
+                tr("Unsafed data non-writable files"),
+                JOptionPane.DEFAULT_OPTION,
+                JOptionPane.WARNING_MESSAGE
+        );
+    }
+
+    protected boolean confirmSaveLayerInfosOK() {
+        List<SaveLayerInfo> layerInfos = model.getLayersWithConflictsAndUploadRequest();
+        if (!layerInfos.isEmpty()) {
+            warnLayersWithConflictsAndUploadRequest(layerInfos);
+            return false;
+        }
+
+        layerInfos = model.getLayersWithoutFilesAndSaveRequest();
+        if (!layerInfos.isEmpty()) {
+            warnLayersWithoutFilesAndSaveRequest(layerInfos);
+            return false;
+        }
+
+        layerInfos = model.getLayersWithIllegalFilesAndSaveRequest();
+        if (!layerInfos.isEmpty()) {
+            warnLayersWithIllegalFilesAndSaveRequest(layerInfos);
+            return false;
+        }
+
+        return true;
+    }
+
+    protected void setUserAction(UserAction action) {
+        this.action = action;
+    }
+
+    class WindowClosingAdapter extends WindowAdapter {
+        @Override
+        public void windowClosing(WindowEvent e) {
+            cancelAction.cancel();
+        }
+    }
+
+    class CancelAction extends AbstractAction {
+        public CancelAction() {
+            putValue(NAME, tr("Cancel"));
+            putValue(SHORT_DESCRIPTION, tr("Close this dialog and resume editing in JOSM"));
+            putValue(SMALL_ICON, ImageProvider.get("cancel"));
+        }
+
+        protected void cancelWhenInEditingModel() {
+            setUserAction(UserAction.CANCEL);
+            setVisible(false);
+        }
+
+        protected void cancelWhenInSaveAndUploadingMode() {
+            cancelSafeAndUploadTask();
+        }
+
+        public void cancel() {
+            switch(model.getMode()) {
+                case EDITING_DATA: cancelWhenInEditingModel(); break;
+                case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); break;
+            }
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            cancel();
+        }
+    }
+
+    class DiscardAndProceedAction extends AbstractAction  implements PropertyChangeListener {
+        public DiscardAndProceedAction() {
+            initForDiscardAndExit();
+        }
+
+        public void initForDiscardAndExit() {
+            putValue(NAME, tr("Discard and Exit"));
+            putValue(SHORT_DESCRIPTION, tr("Exit JOSM without saving. Unsaved changes are lost."));
+            putValue(SMALL_ICON, ImageProvider.get("exit"));
+        }
+
+        public void initForDiscardAndDelete() {
+            putValue(NAME, tr("Discard and Delete"));
+            putValue(SHORT_DESCRIPTION, tr("Delete layers without saving. Unsaved changes are lost."));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            setUserAction(UserAction.PROCEED);
+            setVisible(false);
+        }
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
+                Mode mode = (Mode)evt.getNewValue();
+                switch(mode) {
+                    case EDITING_DATA: setEnabled(true); break;
+                    case UPLOADING_AND_SAVING: setEnabled(false); break;
+                }
+            }
+        }
+    }
+
+    class SaveAndProceedAction extends AbstractAction implements PropertyChangeListener {
+        public SaveAndProceedAction() {
+            initForSaveAndExit();
+        }
+
+        public void initForSaveAndExit() {
+            putValue(NAME, tr("Save and Exit"));
+            putValue(SHORT_DESCRIPTION, tr("Exit JOSM with saving. Unsaved changes are uploaded and/or saved."));
+            putValue(SMALL_ICON, ImageProvider.get("exit"));
+        }
+
+        public void initForSaveAndDelete() {
+            putValue(NAME, tr("Save and Delete"));
+            putValue(SHORT_DESCRIPTION, tr("Save/Upload layers before deleting. Unsaved changes are not lost."));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if (! confirmSaveLayerInfosOK())
+                return;
+            launchSafeAndUploadTask();
+        }
+
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
+                SaveLayersModel.Mode mode = (SaveLayersModel.Mode)evt.getNewValue();
+                switch(mode) {
+                    case EDITING_DATA: setEnabled(true); break;
+                    case UPLOADING_AND_SAVING: setEnabled(false); break;
+                }
+            }
+        }
+    }
+
+    /**
+     * This is the asynchronous task which uploads modified layers to the server and
+     * saves them to files, if requested by the user.
+     * 
+     */
+    protected class SaveAndUploadTask implements Runnable {
+
+        private SaveLayersModel model;
+        private ProgressMonitor monitor;
+        private ExecutorService worker;
+        private boolean cancelled;
+        private Future<?> currentFuture;
+        private AbstractIOTask currentTask;
+
+        public SaveAndUploadTask(SaveLayersModel model, ProgressMonitor monitor) {
+            this.model = model;
+            this.monitor = monitor;
+            this.worker = Executors.newSingleThreadExecutor();
+        }
+
+        protected void uploadLayers(List<SaveLayerInfo> toUpload) {
+            for (final SaveLayerInfo layerInfo: toUpload) {
+                if (cancelled) {
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                    continue;
+                }
+                monitor.subTask(tr("Preparing layer ''{0}'' for upload ...", layerInfo.getName()));
+
+                if (!new UploadAction().checkPreUploadConditions(layerInfo.getLayer())) {
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                    continue;
+                }
+
+                currentTask = new UploadLayerTask(layerInfo.getLayer(), monitor);
+                currentFuture = worker.submit(currentTask);
+                try {
+                    // wait for the asynchronous task to complete
+                    //
+                    currentFuture.get();
+                } catch(CancellationException e) {
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                } catch(Exception e) {
+                    e.printStackTrace();
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                    ExceptionDialogUtil.explainException(e);
+                }
+                if (currentTask.isCancelled()) {
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                } else if (currentTask.isFailed()) {
+                    currentTask.getLastException().printStackTrace();
+                    ExceptionDialogUtil.explainException(currentTask.getLastException());
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                } else {
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.OK);
+                }
+                currentTask = null;
+                currentFuture = null;
+            }
+        }
+
+        protected void saveLayers(List<SaveLayerInfo> toSave) {
+            for (final SaveLayerInfo layerInfo: toSave) {
+                if (cancelled) {
+                    model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                    continue;
+                }
+                currentTask= new SaveLayerTask(layerInfo, monitor);
+                currentFuture = worker.submit(currentTask);
+
+                try {
+                    // wait for the asynchronous task to complete
+                    //
+                    currentFuture.get();
+                } catch(CancellationException e) {
+                    model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                } catch(Exception e) {
+                    e.printStackTrace();
+                    model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                    ExceptionDialogUtil.explainException(e);
+                }
+                if (currentTask.isCancelled()) {
+                    model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                } else if (currentTask.isFailed()) {
+                    if (currentTask.getLastException() != null) {
+                        currentTask.getLastException().printStackTrace();
+                        ExceptionDialogUtil.explainException(currentTask.getLastException());
+                    }
+                    model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.FAILED);
+                } else {
+                    model.setSaveState(layerInfo.getLayer(), UploadOrSaveState.OK);
+                }
+                this.currentTask = null;
+                this.currentFuture = null;
+            }
+        }
+
+        protected void warnBecauseOfUnsavedData() {
+            int numProblems = model.getNumCancel() + model.getNumFailed();
+            if (numProblems == 0) return;
+            String msg = tr(
+                    "<html>An upload and/or save operation of one layer with modifications<br>"
+                    + "was cancelled or has been failed.</html>",
+                    "<html>Upload and/or save operations of {0} layers with modifications<br>"
+                    + "were cancelled or have been failed.</html>",
+                    numProblems,
+                    numProblems
+            );
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    msg,
+                    tr("Incomplete upload and/or save"),
+                    JOptionPane.WARNING_MESSAGE
+            );
+        }
+
+        public void run() {
+            model.setMode(SaveLayersModel.Mode.UPLOADING_AND_SAVING);
+            List<SaveLayerInfo> toUpload = model.getLayersToUpload();
+            if (!toUpload.isEmpty()) {
+                uploadLayers(toUpload);
+            }
+            List<SaveLayerInfo> toSave = model.getLayersToSave();
+            if (!toSave.isEmpty()) {
+                saveLayers(toSave);
+            }
+            model.setMode(SaveLayersModel.Mode.EDITING_DATA);
+            if (model.hasUnsavedData()) {
+                warnBecauseOfUnsavedData();
+                model.setMode(Mode.EDITING_DATA);
+                if (cancelled) {
+                    setUserAction(UserAction.CANCEL);
+                    setVisible(false);
+                }
+            } else {
+                setUserAction(UserAction.PROCEED);
+                setVisible(false);
+            }
+        }
+
+        public void cancel() {
+            if (currentTask != null) {
+                currentTask.cancel();
+            }
+            cancelled = true;
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java	(revision 2025)
@@ -0,0 +1,219 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+public class SaveLayersModel extends DefaultTableModel {
+    static final public String MODE_PROP = SaveLayerInfo.class.getName() + ".mode";
+    public enum Mode {
+        EDITING_DATA,
+        UPLOADING_AND_SAVING
+    }
+
+    private List<SaveLayerInfo> layerInfo;
+    private Mode mode;
+    private PropertyChangeSupport support;
+
+    public SaveLayersModel() {
+        mode = Mode.EDITING_DATA;
+        support = new PropertyChangeSupport(this);
+    }
+
+    public void addPropertyChangeListener(PropertyChangeListener l) {
+        support.addPropertyChangeListener(l);
+    }
+
+    public void removePropertyChangeListener(PropertyChangeListener l) {
+        support.removePropertyChangeListener(l);
+    }
+
+    protected void fireModeChanged(Mode oldValue, Mode newValue) {
+        support.firePropertyChange(MODE_PROP, oldValue, newValue);
+    }
+
+    public void setMode(Mode newValue) {
+        Mode oldValue = this.mode;
+        this.mode = newValue;
+        fireModeChanged(oldValue, newValue);
+    }
+
+    public Mode getMode() {
+        return mode;
+    }
+
+    public void populate(List<OsmDataLayer> layers) {
+        layerInfo = new ArrayList<SaveLayerInfo>();
+        if (layers == null) return;
+        for (OsmDataLayer layer: layers) {
+            layerInfo.add(new SaveLayerInfo(layer));
+        }
+        Collections.sort(
+                layerInfo,
+                new Comparator<SaveLayerInfo>() {
+                    public int compare(SaveLayerInfo o1, SaveLayerInfo o2) {
+                        return o1.compareTo(o2);
+                    }
+                }
+        );
+        fireTableDataChanged();
+    }
+
+    @Override
+    public int getRowCount() {
+        if (layerInfo == null) return 0;
+        return layerInfo.size();
+    }
+
+    @Override
+    public Object getValueAt(int row, int column) {
+        if (layerInfo == null) return null;
+        return layerInfo.get(row);
+    }
+
+    @Override
+    public boolean isCellEditable(int row, int column) {
+        return column == 3 || column == 4 || column == 5;
+    }
+
+    @Override
+    public void setValueAt(Object value, int row, int column) {
+        switch(column) {
+            case 3 /* file name */:
+                this.layerInfo.get(row).setFile((File)value);
+                this.layerInfo.get(row).setDoSaveToFile(true);
+                break;
+            case 4 /* upload */: this.layerInfo.get(row).setDoUploadToServer((Boolean)value);break;
+            case 5 /* save */: this.layerInfo.get(row).setDoSaveToFile((Boolean)value);break;
+        }
+        fireTableDataChanged();
+    }
+
+    public List<SaveLayerInfo> getSafeLayerInfo() {
+        return this.layerInfo;
+    }
+
+    public List<SaveLayerInfo> getLayersWithoutFilesAndSaveRequest() {
+        List<SaveLayerInfo> ret =new ArrayList<SaveLayerInfo>();
+        for (SaveLayerInfo info: layerInfo) {
+            if (info.isDoSaveToFile() && info.getFile() == null) {
+                ret.add(info);
+            }
+        }
+        return ret;
+    }
+
+    public List<SaveLayerInfo> getLayersWithIllegalFilesAndSaveRequest() {
+        List<SaveLayerInfo> ret =new ArrayList<SaveLayerInfo>();
+        for (SaveLayerInfo info: layerInfo) {
+            if (info.isDoSaveToFile() && info.getFile() != null && ! info.getFile().canWrite()) {
+                ret.add(info);
+            }
+        }
+        return ret;
+    }
+
+    public List<SaveLayerInfo> getLayersWithConflictsAndUploadRequest() {
+        List<SaveLayerInfo> ret =new ArrayList<SaveLayerInfo>();
+        for (SaveLayerInfo info: layerInfo) {
+            if (info.isDoUploadToServer() && !info.getLayer().getConflicts().isEmpty()) {
+                ret.add(info);
+            }
+        }
+        return ret;
+    }
+
+    public List<SaveLayerInfo> getLayersToUpload() {
+        List<SaveLayerInfo> ret =new ArrayList<SaveLayerInfo>();
+        for (SaveLayerInfo info: layerInfo) {
+            if (info.isDoUploadToServer()) {
+                ret.add(info);
+            }
+        }
+        return ret;
+    }
+
+    public List<SaveLayerInfo> getLayersToSave() {
+        List<SaveLayerInfo> ret =new ArrayList<SaveLayerInfo>();
+        for (SaveLayerInfo info: layerInfo) {
+            if (info.isDoSaveToFile()) {
+                ret.add(info);
+            }
+        }
+        return ret;
+    }
+
+
+    public void setUploadState(OsmDataLayer layer, UploadOrSaveState state) {
+        SaveLayerInfo info = getSaveLayerInfo(layer);
+        if (info != null) {
+            info.setUploadState(state);
+        }
+        fireTableDataChanged();
+    }
+
+    public void setSaveState(OsmDataLayer layer, UploadOrSaveState state) {
+        SaveLayerInfo info = getSaveLayerInfo(layer);
+        if (info != null) {
+            info.setSaveState(state);
+        }
+        fireTableDataChanged();
+    }
+
+    public SaveLayerInfo getSaveLayerInfo(OsmDataLayer layer) {
+        for (SaveLayerInfo info: this.layerInfo) {
+            if (info.getLayer() == layer)
+                return info;
+        }
+        return null;
+    }
+
+    public void resetSaveAndUploadState() {
+        for (SaveLayerInfo info: layerInfo) {
+            info.setSaveState(null);
+            info.setUploadState(null);
+        }
+    }
+
+    public boolean hasUnsavedData() {
+        for (SaveLayerInfo info: layerInfo) {
+            if (info.isDoUploadToServer() && ! UploadOrSaveState.OK.equals(info.getUploadState()))
+                return true;
+            if (info.isDoSaveToFile() && ! UploadOrSaveState.OK.equals(info.getSaveState()))
+                return true;
+        }
+        return false;
+    }
+
+    public int getNumCancel() {
+        int ret = 0;
+        for (SaveLayerInfo info: layerInfo) {
+            if (UploadOrSaveState.CANCELLED.equals(info.getSaveState())
+                    || UploadOrSaveState.CANCELLED.equals(info.getUploadState())) {
+                ret++;
+            }
+        }
+        return ret;
+    }
+
+    public int getNumFailed() {
+        int ret = 0;
+        for (SaveLayerInfo info: layerInfo) {
+            if (UploadOrSaveState.FAILED.equals(info.getSaveState())
+                    || UploadOrSaveState.FAILED.equals(info.getUploadState())) {
+                ret++;
+            }
+        }
+        return ret;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java	(revision 2025)
@@ -0,0 +1,26 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JTable;
+
+import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
+
+class SaveLayersTable extends JTable implements PropertyChangeListener {
+    public SaveLayersTable(SaveLayersModel model) {
+        super(model, new SaveLayersTableColumnModel());
+        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+    }
+
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
+            Mode mode = (Mode)evt.getNewValue();
+            switch(mode) {
+                case EDITING_DATA: setEnabled(true);
+                case UPLOADING_AND_SAVING: setEnabled(false);
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/UploadAndSaveProgressRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadAndSaveProgressRenderer.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadAndSaveProgressRenderer.java	(revision 2025)
@@ -0,0 +1,92 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import org.openstreetmap.josm.gui.io.SaveLayersModel.Mode;
+import org.openstreetmap.josm.gui.progress.ProgressRenderer;
+
+class UploadAndSaveProgressRenderer extends JPanel implements ProgressRenderer, PropertyChangeListener {
+
+    private JLabel lblTaskTitle;
+    private JLabel lblCustomText;
+    private JProgressBar progressBar;
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(5,0,0,5);
+        add(lblTaskTitle = new JLabel(""), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(5,0,0,5);
+        add(lblCustomText = new JLabel(""), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(5,0,0,5);
+        add(progressBar = new JProgressBar(JProgressBar.HORIZONTAL), gc);
+    }
+
+    public UploadAndSaveProgressRenderer() {
+        build();
+        // initially not visible
+        setVisible(false);
+    }
+
+    public void setCustomText(String message) {
+        lblCustomText.setText(message);
+        repaint();
+    }
+
+    public void setIndeterminate(boolean indeterminate) {
+        progressBar.setIndeterminate(indeterminate);
+        repaint();
+    }
+
+    public void setMaximum(int maximum) {
+        progressBar.setMaximum(maximum);
+        repaint();
+    }
+
+    public void setTaskTitle(String taskTitle) {
+        lblTaskTitle.setText(taskTitle);
+        repaint();
+    }
+
+    public void setValue(int value) {
+        progressBar.setValue(value);
+        repaint();
+    }
+
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) {
+            Mode mode = (Mode)evt.getNewValue();
+            switch(mode) {
+                case EDITING_DATA: setVisible(false); break;
+                case UPLOADING_AND_SAVING: setVisible(true); break;
+            }
+        }
+        getParent().validate();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 2025)
@@ -0,0 +1,88 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.OsmServerWriter;
+
+/**
+ * UploadLayerTask uploads the data managed by an {@see OsmDataLayer} asynchronously.
+ * 
+ * <pre>
+ *     ExecutorService executorService = ...
+ *     UploadLayerTask task = new UploadLayerTask(layer, monitor);
+ *     Future<?> taskFuture = executorServce.submit(task)
+ *     try {
+ *        // wait for the task to complete
+ *        taskFuture.get();
+ *     } catch(Exception e) {
+ *        e.printStackTracek();
+ *     }
+ * </pre>
+ */
+class UploadLayerTask extends AbstractIOTask implements Runnable {
+    private OsmServerWriter writer;
+    private OsmDataLayer layer;
+    private ProgressMonitor monitor;
+
+    /**
+     * 
+     * @param layer the layer. Must not be null.
+     * @param monitor  a progress monitor. If monitor is null, uses {@see NullProgressMonitor#INSTANCE}
+     * @throws IllegalArgumentException thrown, if layer is null
+     */
+    public UploadLayerTask(OsmDataLayer layer, ProgressMonitor monitor) {
+        if (layer == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", layer));
+        if (monitor == null) {
+            monitor = NullProgressMonitor.INSTANCE;
+        }
+        this.layer = layer;
+        this.monitor = monitor;
+    }
+
+    @Override
+    public void run() {
+        monitor.subTask(tr("Preparing primitives to upload ..."));
+        Collection<OsmPrimitive> toUpload = new APIDataSet(layer.data).getPrimitives();
+        if (toUpload.isEmpty())
+            return;
+        writer = new OsmServerWriter();
+        try {
+            ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
+            if (isCancelled()) return;
+            writer.uploadOsm(layer.data.version, toUpload, m);
+        } catch (Exception sxe) {
+            if (isCancelled()) {
+                System.out.println("Ignoring exception caught because upload is cancelled. Exception is: " + sxe.toString());
+                return;
+            }
+            setLastException(sxe);
+        }
+
+        if (isCancelled())
+            return;
+        layer.cleanupAfterUpload(writer.getProcessedPrimitives());
+        DataSet.fireSelectionChanged(layer.data.getSelected());
+        layer.fireDataChange();
+        layer.onPostUploadToServer();
+    }
+
+    @Override
+    public void cancel() {
+        // make sure the the softCancel operation is serialized with
+        // blocks which can be interrupted.
+        setCancelled(true);
+        if (writer != null) {
+            writer.disconnectActiveConnection();
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/io/UploadOrSaveState.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadOrSaveState.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadOrSaveState.java	(revision 2025)
@@ -0,0 +1,17 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+public enum UploadOrSaveState {
+    /**
+     * a data layer was successfully saved or upload to the server
+     */
+    OK,
+    /**
+     * uploading or saving a data layer has failed
+     */
+    FAILED,
+    /**
+     * uploading or saving a data layer was cancelled
+     */
+    CANCELLED
+}
Index: trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 2025)
@@ -895,5 +895,5 @@
                 }
             }
-            new DownloadOsmTaskList().download(false, toDownload, new PleaseWaitProgressMonitor());
+            new DownloadOsmTaskList().download(false, toDownload, new PleaseWaitProgressMonitor(tr("Download data")));
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/layer/Layer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/Layer.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/layer/Layer.java	(revision 2025)
@@ -45,5 +45,5 @@
 
     /** keeps track of property change listeners */
-    private PropertyChangeSupport propertyChangeSupport;
+    protected PropertyChangeSupport propertyChangeSupport;
 
     /**
Index: trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 2025)
@@ -71,4 +71,25 @@
  */
 public class OsmDataLayer extends Layer {
+    static public final String REQUIRES_SAVE_TO_DISK_PROP = OsmDataLayer.class.getName() + ".requiresSaveToDisk";
+    static public final String REQUIRES_UPLOAD_TO_SERVER_PROP = OsmDataLayer.class.getName() + ".requiresUploadToServer";
+
+    private boolean requiresSaveToFile = false;
+    private boolean requiresUploadToServer = false;
+
+    protected void setRequiresSaveToFile(boolean newValue) {
+        boolean oldValue = requiresSaveToFile;
+        requiresSaveToFile = newValue;
+        if (oldValue != newValue) {
+            propertyChangeSupport.firePropertyChange(REQUIRES_SAVE_TO_DISK_PROP, oldValue, newValue);
+        }
+    }
+
+    protected void setRequiresUploadToServer(boolean newValue) {
+        boolean oldValue = requiresUploadToServer;
+        requiresUploadToServer = newValue;
+        if (oldValue != newValue) {
+            propertyChangeSupport.firePropertyChange(REQUIRES_UPLOAD_TO_SERVER_PROP, oldValue, newValue);
+        }
+    }
 
     /** the global counter for created data layers */
@@ -96,5 +117,5 @@
         private void inc(final OsmPrimitive osm, final int i) {
             normal[i]++;
-            if (osm.deleted) {
+            if (osm.isDeleted()) {
                 deleted[i]++;
             }
@@ -113,7 +134,4 @@
     }
 
-    public interface ModifiedChangedListener {
-        void modifiedChanged(boolean value, OsmDataLayer source);
-    }
     public interface CommandQueueListener {
         void commandChanged(int queueSize, int redoSize);
@@ -130,14 +148,4 @@
     private ConflictCollection conflicts;
 
-    /**
-     * Whether the data of this layer was modified during the session.
-     */
-    private boolean modified = false;
-    /**
-     * Whether the data was modified due an upload of the data to the server.
-     */
-    public boolean uploadedModified = false;
-
-    public final LinkedList<ModifiedChangedListener> listenerModified = new LinkedList<ModifiedChangedListener>();
     public final LinkedList<DataChangeListener> listenerDataChanged = new LinkedList<DataChangeListener>();
 
@@ -310,16 +318,7 @@
     @Override public void visitBoundingBox(final BoundingXYVisitor v) {
         for (final Node n : data.nodes)
-            if (!n.deleted && !n.incomplete) {
+            if (!n.isDeleted() && !n.incomplete) {
                 v.visit(n);
             }
-    }
-
-    /**
-     * Cleanup the layer after save to disk. Just marks the layer as unmodified.
-     * Leaves the undo/redo stack unchanged.
-     *
-     */
-    public void cleanupAfterSaveToDisk() {
-        setModified(false);
     }
 
@@ -333,5 +332,4 @@
      */
     public void cleanupAfterUpload(final Collection<OsmPrimitive> processed) {
-
         // return immediately if an upload attempt failed
         if (processed == null || processed.isEmpty())
@@ -351,6 +349,4 @@
             cleanIterator(it, processedSet);
         }
-
-        setModified(true);
     }
 
@@ -367,20 +363,7 @@
         if (!processed.remove(osm))
             return;
-        osm.modified = false;
-        if (osm.deleted) {
+        osm.setModified(false);
+        if (osm.isDeleted()) {
             it.remove();
-        }
-    }
-
-    public boolean isModified() {
-        return modified;
-    }
-
-    public void setModified(final boolean modified) {
-        if (modified == this.modified)
-            return;
-        this.modified = modified;
-        for (final ModifiedChangedListener l : listenerModified) {
-            l.modifiedChanged(modified, this);
         }
     }
@@ -392,5 +375,5 @@
         int size = 0;
         for (final OsmPrimitive osm : list)
-            if (!osm.deleted) {
+            if (!osm.isDeleted()) {
                 size++;
             }
@@ -446,4 +429,6 @@
 
     public void fireDataChange() {
+        setRequiresSaveToFile(true);
+        setRequiresUploadToServer(true);
         for (DataChangeListener dcl : listenerDataChanged) {
             dcl.dataChanged(this);
@@ -456,5 +441,5 @@
         HashSet<Node> doneNodes = new HashSet<Node>();
         for (Way w : data.ways) {
-            if (w.incomplete || w.deleted) {
+            if (w.incomplete || w.isDeleted()) {
                 continue;
             }
@@ -468,5 +453,5 @@
             ArrayList<WayPoint> trkseg = null;
             for (Node n : w.getNodes()) {
-                if (n.incomplete || n.deleted) {
+                if (n.incomplete || n.isDeleted()) {
                     trkseg = null;
                     continue;
@@ -492,5 +477,5 @@
         // records them?
         for (Node n : data.nodes) {
-            if (n.incomplete || n.deleted || doneNodes.contains(n)) {
+            if (n.incomplete || n.isDeleted() || doneNodes.contains(n)) {
                 continue;
             }
@@ -546,3 +531,55 @@
         return conflicts;
     }
+
+    /**
+     * Replies true if the data managed by this layer needs to be uploaded to
+     * the server because it contains at least one modified primitive.
+     * 
+     * @return true if the data managed by this layer needs to be uploaded to
+     * the server because it contains at least one modified primitive; false,
+     * otherwise
+     */
+    public boolean requiresUploadToServer() {
+        return requiresUploadToServer;
+    }
+
+    /**
+     * Replies true if the data managed by this layer needs to be saved to
+     * a file. Only replies true if a file is assigned to this layer and
+     * if the data managed by this layer has been modified since the last
+     * save operation to the file.
+     * 
+     * @return true if the data managed by this layer needs to be saved to
+     * a file
+     */
+    public boolean requiresSaveToFile() {
+        return getAssociatedFile() != null && requiresSaveToFile;
+    }
+
+    /**
+     * Initializes the layer after a successful load of OSM data from a file
+     * 
+     */
+    public void onPostLoadFromFile() {
+        setRequiresSaveToFile(false);
+        setRequiresUploadToServer(data.isModified());
+    }
+
+    /**
+     * Initializes the layer after a successful save of OSM data to a file
+     * 
+     */
+    public void onPostSaveToFile() {
+        setRequiresSaveToFile(false);
+        setRequiresUploadToServer(data.isModified());
+    }
+
+    /**
+     * Initializes the layer after a successful upload to the server
+     * 
+     */
+    public void onPostUploadToServer() {
+        setRequiresUploadToServer(false);
+        // keep requiresSaveToDisk unchanged
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/progress/PleaseWaitProgressMonitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/progress/PleaseWaitProgressMonitor.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/gui/progress/PleaseWaitProgressMonitor.java	(revision 2025)
@@ -28,7 +28,9 @@
 
     private PleaseWaitDialog dialog;
+    private String windowTitle;
 
-    public PleaseWaitProgressMonitor() {
+    public PleaseWaitProgressMonitor(String windowTitle) {
         this(JOptionPane.getFrameForComponent(Main.map));
+        this.windowTitle = windowTitle;
     }
 
@@ -86,4 +88,7 @@
                     throw new ProgressException("PleaseWaitDialog parent must be either Frame or Dialog");
 
+                if (windowTitle != null) {
+                    dialog.setTitle(windowTitle);
+                }
                 dialog.cancel.setEnabled(true);
                 dialog.setCustomText("");
Index: trunk/src/org/openstreetmap/josm/gui/progress/ProgressRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/progress/ProgressRenderer.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/progress/ProgressRenderer.java	(revision 2025)
@@ -0,0 +1,15 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.progress;
+
+/**
+ * Swing components can implement this interface and use a {@see SwingRenderingProgressMonitor}
+ * to render progress information.
+ * 
+ */
+public interface ProgressRenderer {
+    void setTaskTitle(String taskTitle);
+    void setCustomText(String message);
+    void setIndeterminate(boolean indeterminate);
+    void setMaximum(int maximum);
+    void setValue(int value);
+}
Index: trunk/src/org/openstreetmap/josm/gui/progress/SwingRenderingProgressMonitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/progress/SwingRenderingProgressMonitor.java	(revision 2025)
+++ trunk/src/org/openstreetmap/josm/gui/progress/SwingRenderingProgressMonitor.java	(revision 2025)
@@ -0,0 +1,100 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.progress;
+
+import javax.swing.SwingUtilities;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * SwingRenderingProgressMonitor is progress monitor which delegates the rendering
+ * of progress information to a {@see ProgressRenderer}.
+ * Methods of the progress renderer are always called on the Swing EDT.
+ *
+ */
+public class SwingRenderingProgressMonitor extends AbstractProgressMonitor {
+    private static final int PROGRESS_BAR_MAX = 100;
+    private int currentProgressValue = 0;
+    private ProgressRenderer delegate;
+
+    /**
+     * 
+     * @param delegate the delegate which renders the progress information. Must not be null.
+     * @throws IllegalArgumentException thrown if delegate is null
+     * 
+     */
+    public SwingRenderingProgressMonitor(ProgressRenderer delegate) {
+        super(new CancelHandler());
+        if (delegate == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "delegate"));
+        this.delegate = delegate;
+    }
+
+    private void doInEDT(Runnable runnable) {
+        if (SwingUtilities.isEventDispatchThread()) {
+            runnable.run();
+        } else {
+            SwingUtilities.invokeLater(runnable);
+        }
+    }
+
+    @Override
+    public void doBeginTask() {
+        doInEDT(new Runnable() {
+            public void run() {
+                delegate.setCustomText("");
+                delegate.setMaximum(PROGRESS_BAR_MAX);
+            }
+        });
+    }
+
+    @Override
+    public void doFinishTask() {
+        // do nothing
+    }
+
+    @Override
+    protected void updateProgress(double progressValue) {
+        final int newValue = (int)(progressValue * PROGRESS_BAR_MAX);
+        if (newValue != currentProgressValue) {
+            currentProgressValue = newValue;
+            doInEDT(new Runnable() {
+                public void run() {
+                    delegate.setValue(currentProgressValue);
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void doSetCustomText(final String title) {
+        checkState(State.IN_TASK, State.IN_SUBTASK);
+        doInEDT(new Runnable() {
+            public void run() {
+                delegate.setCustomText(title);
+            }
+        });
+    }
+
+    @Override
+    protected void doSetTitle(final String title) {
+        checkState(State.IN_TASK, State.IN_SUBTASK);
+        doInEDT(new Runnable() {
+            public void run() {
+                delegate.setTaskTitle(title);
+            }
+        });
+    }
+
+    @Override
+    protected void doSetIntermediate(final boolean value) {
+        doInEDT(new Runnable() {
+            public void run() {
+                delegate.setIndeterminate(value);
+            }
+        });
+    }
+
+    @Override
+    protected void doSetErrorMessage(String message) {
+        // Do nothing
+    }
+}
Index: trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 2025)
@@ -168,6 +168,6 @@
     public MultiFetchServerObjectReader append(Node node) {
         if (node == null) return this;
-        if (node.id == 0) return this;
-        remember(node.id, OsmPrimitiveType.NODE);
+        if (node.getId() == 0) return this;
+        remember(node.getId(), OsmPrimitiveType.NODE);
         return this;
     }
@@ -182,11 +182,11 @@
     public MultiFetchServerObjectReader append(Way way) {
         if (way == null) return this;
-        if (way.id == 0) return this;
+        if (way.getId() == 0) return this;
         for (Node node: way.getNodes()) {
-            if (node.id > 0) {
-                remember(node.id, OsmPrimitiveType.NODE);
-            }
-        }
-        remember(way.id, OsmPrimitiveType.WAY);
+            if (node.getId() > 0) {
+                remember(node.getId(), OsmPrimitiveType.NODE);
+            }
+        }
+        remember(way.getId(), OsmPrimitiveType.WAY);
         return this;
     }
@@ -201,11 +201,11 @@
     public MultiFetchServerObjectReader append(Relation relation) {
         if (relation == null) return this;
-        if (relation.id == 0) return this;
-        remember(relation.id, OsmPrimitiveType.RELATION);
+        if (relation.getId() == 0) return this;
+        remember(relation.getId(), OsmPrimitiveType.RELATION);
         for (RelationMember member : relation.getMembers()) {
             if (OsmPrimitiveType.from(member.member).equals(OsmPrimitiveType.RELATION)) {
                 // avoid infinite recursion in case of cyclic dependencies in relations
                 //
-                if (relations.contains(member.member.id)) {
+                if (relations.contains(member.member.getId())) {
                     continue;
                 }
@@ -373,7 +373,7 @@
                 String msg = "";
                 switch(type) {
-                case NODE: msg = tr("Fetching node with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;
-                case WAY: msg = tr("Fetching way with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;
-                case RELATION: msg = tr("Fetching relation with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;
+                    case NODE: msg = tr("Fetching node with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;
+                    case WAY: msg = tr("Fetching way with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;
+                    case RELATION: msg = tr("Fetching relation with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;
                 }
                 progressMonitor.setCustomText(msg);
@@ -411,7 +411,7 @@
         String msg = "";
         switch(type) {
-        case NODE: msg = tr("Fetching a package of nodes from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break;
-        case WAY:  msg = tr("Fetching a package of ways from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break;
-        case RELATION:  msg = tr("Fetching a package of relations from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break;
+            case NODE: msg = tr("Fetching a package of nodes from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break;
+            case WAY:  msg = tr("Fetching a package of ways from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break;
+            case RELATION:  msg = tr("Fetching a package of relations from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break;
         }
         progressMonitor.setCustomText(msg);
Index: trunk/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2025)
@@ -251,13 +251,13 @@
         if (version.equals("0.5")) {
             // legacy mode does not return the new object version.
-            sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.id, toXml(osm, true));
+            sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.getId(), toXml(osm, true));
         } else {
             String ret = null;
             // normal mode (0.6 and up) returns new object version.
             try {
-                ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.id, toXml(osm, true));
+                ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.getId(), toXml(osm, true));
                 osm.version = Integer.parseInt(ret.trim());
             } catch(NumberFormatException e) {
-                throw new OsmTransferException(tr("unexpected format of new version of modified primitive ''{0}'', got ''{1}''", osm.id, ret));
+                throw new OsmTransferException(tr("unexpected format of new version of modified primitive ''{0}'', got ''{1}''", osm.getId(), ret));
             }
         }
@@ -303,8 +303,8 @@
      */
     public void stopChangeset(ProgressMonitor progressMonitor) throws OsmTransferException {
-        progressMonitor.beginTask(tr("Closing changeset..."));
+        progressMonitor.beginTask(tr("Closing changeset {0}...", changeset.getId()));
         try {
             initialize();
-            sendRequest("PUT", "changeset" + "/" + changeset.id + "/close", null);
+            sendRequest("PUT", "changeset" + "/" + changeset.getId() + "/close", null);
             changeset = null;
         } finally {
@@ -341,5 +341,5 @@
             String diff = duv.getDocument();
             try {
-                String diffresult = sendRequest("POST", "changeset/" + changeset.id + "/upload", diff);
+                String diffresult = sendRequest("POST", "changeset/" + changeset.getId() + "/upload", diff);
                 DiffResultReader.parseDiffResult(diffresult, list, processed, duv.getNewIdMap(),
                         progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
Index: trunk/src/org/openstreetmap/josm/io/OsmExporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmExporter.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/io/OsmExporter.java	(revision 2025)
@@ -76,5 +76,5 @@
                 tmpFile.delete();
             }
-            layer.cleanupAfterSaveToDisk();
+            layer.onPostSaveToFile();
         } catch (IOException e) {
             e.printStackTrace();
Index: trunk/src/org/openstreetmap/josm/io/OsmImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmImporter.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/io/OsmImporter.java	(revision 2025)
@@ -50,4 +50,5 @@
         Main.main.addLayer(layer);
         layer.fireDataChange();
+        layer.onPostLoadFromFile();
     }
 }
Index: trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java	(revision 2025)
@@ -95,7 +95,7 @@
                 String msg = "";
                 switch(OsmPrimitiveType.from(osm)) {
-                case NODE: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading node ''{4}'' (id: {5})"); break;
-                case WAY: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading way ''{4}'' (id: {5})"); break;
-                case RELATION: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading relation ''{4}'' (id: {5})"); break;
+                    case NODE: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading node ''{4}'' (id: {5})"); break;
+                    case WAY: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading way ''{4}'' (id: {5})"); break;
+                    case RELATION: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading relation ''{4}'' (id: {5})"); break;
                 }
                 progressMonitor.subTask(
@@ -105,6 +105,6 @@
                                 primitives.size(),
                                 time_left_str,
-                                osm.getName() == null ? osm.id : osm.getName(),
-                                        osm.id));
+                                osm.getName() == null ? osm.getId() : osm.getName(),
+                                        osm.getId()));
                 makeApiRequest(osm,progressMonitor);
                 processed.add(osm);
@@ -117,8 +117,14 @@
         } finally {
             try {
-                api.stopChangeset(progressMonitor.createSubTaskMonitor(0, false));
+                // starting the changeset may have failed, for instance because the user
+                // cancelled the upload task. Only close the changeset if we currently have
+                // an open changeset
+
+                if (api.getCurrentChangeset() != null && api.getCurrentChangeset().getId() > 0) {
+                    api.stopChangeset(progressMonitor.createSubTaskMonitor(0, false));
+                }
             } catch(Exception e) {
                 Changeset changeset = api.getCurrentChangeset();
-                String changesetId = (changeset == null ? tr("unknown") : Long.toString(changeset.id));
+                String changesetId = (changeset == null ? tr("unknown") : Long.toString(changeset.getId()));
                 logger.warning(tr("Failed to close changeset {0}, will be closed by server after timeout. Exception was: {1}",
                         changesetId, e.toString()));
@@ -149,5 +155,5 @@
             } catch (Exception ee) {
                 Changeset changeset = api.getCurrentChangeset();
-                String changesetId = (changeset == null ? tr("unknown") : Long.toString(changeset.id));
+                String changesetId = (changeset == null ? tr("unknown") : Long.toString(changeset.getId()));
                 logger.warning(tr("Failed to close changeset {0}, will be closed by server after timeout. Exception was: {1}",
                         changesetId, ee.toString()));
@@ -166,6 +172,4 @@
 
         api.initialize();
-
-        progressMonitor.beginTask("");
 
         try {
@@ -180,6 +184,8 @@
 
             if (useChangeset) {
+                progressMonitor.beginTask(tr("Starting to upload in one request ..."));
                 uploadChangesAsDiffUpload(primitives, progressMonitor);
             } else {
+                progressMonitor.beginTask(tr("Starting to upload with one request per primitive ..."));
                 uploadChangesIndividually(primitives, progressMonitor);
             }
@@ -190,7 +196,7 @@
 
     void makeApiRequest(OsmPrimitive osm, ProgressMonitor progressMonitor) throws OsmTransferException {
-        if (osm.deleted) {
+        if (osm.isDeleted()) {
             api.deletePrimitive(osm, progressMonitor);
-        } else if (osm.id == 0) {
+        } else if (osm.getId() == 0) {
             api.createPrimitive(osm);
         } else {
Index: trunk/src/org/openstreetmap/josm/io/OsmWriter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 2025)
@@ -85,5 +85,5 @@
 
     private boolean shouldWrite(OsmPrimitive osm) {
-        return osm.id != 0 || !osm.deleted;
+        return osm.getId() != 0 || !osm.isDeleted();
     }
 
@@ -155,6 +155,6 @@
      */
     private long getUsedId(OsmPrimitive osm) {
-        if (osm.id != 0)
-            return osm.id;
+        if (osm.getId() != 0)
+            return osm.getId();
         if (usedNewIds.containsKey(osm))
             return usedNewIds.get(osm);
@@ -169,7 +169,8 @@
             }
             for (Entry<String, String> e : osm.entrySet()) {
-                if ((osm instanceof Changeset) || !("created_by".equals(e.getKey())))
+                if ((osm instanceof Changeset) || !("created_by".equals(e.getKey()))) {
                     out.println("    <tag k='"+ XmlWriter.encode(e.getKey()) +
                             "' v='"+XmlWriter.encode(e.getValue())+ "' />");
+                }
             }
             out.println("  </" + tagname + ">");
@@ -193,7 +194,7 @@
         if (!osmConform) {
             String action = null;
-            if (osm.deleted) {
+            if (osm.isDeleted()) {
                 action = "delete";
-            } else if (osm.modified) {
+            } else if (osm.isModified()) {
                 action = "modify";
             }
@@ -209,10 +210,10 @@
             out.print(" user='"+XmlWriter.encode(osm.user.name)+"'");
         }
-        out.print(" visible='"+osm.visible+"'");
+        out.print(" visible='"+osm.isVisible()+"'");
         if (osm.version != -1) {
             out.print(" version='"+osm.version+"'");
         }
-        if (this.changeset != null && this.changeset.id != 0) {
-            out.print(" changeset='"+this.changeset.id+"'" );
+        if (this.changeset != null && this.changeset.getId() != 0) {
+            out.print(" changeset='"+this.changeset.getId()+"'" );
         }
     }
Index: trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 2021)
+++ trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 2025)
@@ -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.breakBecauseUnsavedChanges();
+            handled = Main.saveUnsavedModifications();
         } else if (method.getName().equals("handleAbout")) {
             Main.main.menu.about.actionPerformed(null);
