Index: /trunk/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java	(revision 2599)
@@ -0,0 +1,355 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.net.HttpURLConnection;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.DownloadReferrersAction;
+import org.openstreetmap.josm.actions.UpdateDataAction;
+import org.openstreetmap.josm.actions.UpdateSelectionAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.OsmApiException;
+import org.openstreetmap.josm.io.OsmApiInitializationException;
+import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
+import org.openstreetmap.josm.tools.DateUtils;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public abstract class AbstractUploadTask extends PleaseWaitRunnable {
+    private static final Logger logger = Logger.getLogger(AbstractUploadTask.class.getName());
+
+    public AbstractUploadTask(String title, boolean ignoreException) {
+        super(title, ignoreException);
+    }
+
+    public AbstractUploadTask(String title, ProgressMonitor progressMonitor, boolean ignoreException) {
+        super(title, progressMonitor, ignoreException);
+    }
+
+    public AbstractUploadTask(String title) {
+        super(title);
+    }
+
+    /**
+     * Synchronizes the local state of an {@see OsmPrimitive} with its state on the
+     * server. The method uses an individual GET for the primitive.
+     *
+     * @param id the primitive ID
+     */
+    protected void synchronizePrimitive(final OsmPrimitiveType type, final long id) {
+        // FIXME: should now about the layer this task is running for. might
+        // be different from the current edit layer
+        OsmDataLayer layer = Main.main.getEditLayer();
+        if (layer == null)
+            throw new IllegalStateException(tr("Failed to update primitive with id {0} because current edit layer is null", id));
+        OsmPrimitive p = layer.data.getPrimitiveById(id, type);
+        if (p == null)
+            throw new IllegalStateException(tr("Failed to update primitive with id {0} because current edit layer doesn't include such a primitive", id));
+        Main.worker.execute(new UpdatePrimitivesTask(layer, Collections.singleton(p)));
+    }
+
+    /**
+     * Synchronizes the local state of the dataset with the state on the server.
+     *
+     * Reuses the functionality of {@see UpdateDataAction}.
+     *
+     * @see UpdateDataAction#actionPerformed(ActionEvent)
+     */
+    protected void synchronizeDataSet() {
+        UpdateDataAction act = new UpdateDataAction();
+        act.actionPerformed(new ActionEvent(this,0,""));
+    }
+
+    /**
+     * Handles the case that a conflict in a specific {@see OsmPrimitive} was detected while
+     * uploading
+     *
+     * @param primitiveType  the type of the primitive, either <code>node</code>, <code>way</code> or
+     *    <code>relation</code>
+     * @param id  the id of the primitive
+     * @param serverVersion  the version of the primitive on the server
+     * @param myVersion  the version of the primitive in the local dataset
+     */
+    protected void handleUploadConflictForKnownConflict(final OsmPrimitiveType primitiveType, final long id, String serverVersion, String myVersion) {
+        String lbl = "";
+        switch(primitiveType) {
+        case NODE: lbl =  tr("Synchronize node {0} only", id); break;
+        case WAY: lbl =  tr("Synchronize way {0} only", id); break;
+        case RELATION: lbl =  tr("Synchronize relation {0} only", id); break;
+        }
+        ButtonSpec[] spec = new ButtonSpec[] {
+                new ButtonSpec(
+                        lbl,
+                        ImageProvider.get("updatedata"),
+                        null,
+                        null
+                ),
+                new ButtonSpec(
+                        tr("Synchronize entire dataset"),
+                        ImageProvider.get("updatedata"),
+                        null,
+                        null
+                ),
+                new ButtonSpec(
+                        tr("Cancel"),
+                        ImageProvider.get("cancel"),
+                        null,
+                        null
+                )
+        };
+        String msg =  tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"
+                + "of your nodes, ways, or relations.<br>"
+                + "The conflict is caused by the <strong>{0}</strong> with id <strong>{1}</strong>,<br>"
+                + "the server has version {2}, your version is {3}.<br>"
+                + "<br>"
+                + "Click <strong>{4}</strong> to synchronize the conflicting primitive only.<br>"
+                + "Click <strong>{5}</strong> to synchronize the entire local dataset with the server.<br>"
+                + "Click <strong>{6}</strong> to abort and continue editing.<br></html>",
+                tr(primitiveType.getAPIName()), id, serverVersion, myVersion,
+                spec[0].text, spec[1].text, spec[2].text
+        );
+        int ret = HelpAwareOptionPane.showOptionDialog(
+                Main.parent,
+                msg,
+                tr("Conflicts detected"),
+                JOptionPane.ERROR_MESSAGE,
+                null,
+                spec,
+                spec[0],
+                "/Concepts/Conflict"
+        );
+        switch(ret) {
+        case 0: synchronizePrimitive(primitiveType, id); break;
+        case 1: synchronizeDataSet(); break;
+        default: return;
+        }
+    }
+
+    /**
+     * Handles the case that a conflict was detected while uploading where we don't
+     * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)
+     *
+     */
+    protected void handleUploadConflictForUnknownConflict() {
+        ButtonSpec[] spec = new ButtonSpec[] {
+                new ButtonSpec(
+                        tr("Synchronize entire dataset"),
+                        ImageProvider.get("updatedata"),
+                        null,
+                        null
+                ),
+                new ButtonSpec(
+                        tr("Cancel"),
+                        ImageProvider.get("cancel"),
+                        null,
+                        null
+                )
+        };
+        String msg =  tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"
+                + "of your nodes, ways, or relations.<br>"
+                + "<br>"
+                + "Click <strong>{0}</strong> to synchronize the entire local dataset with the server.<br>"
+                + "Click <strong>{1}</strong> to abort and continue editing.<br></html>",
+                spec[0].text, spec[1].text
+        );
+        int ret = HelpAwareOptionPane.showOptionDialog(
+                Main.parent,
+                msg,
+                tr("Conflicts detected"),
+                JOptionPane.ERROR_MESSAGE,
+                null,
+                spec,
+                spec[0],
+                ht("Concepts/Conflict")
+        );
+        if (ret == 0) {
+            synchronizeDataSet();
+        }
+    }
+
+    /**
+     * Handles the case that a conflict was detected while uploading where we don't
+     * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)
+     *
+     */
+    protected void handleUploadConflictForClosedChangeset(long changsetId, Date d) {
+        String msg =  tr("<html>Uploading <strong>failed</strong> because you''ve been using<br>"
+                + "changeset {0} which was already closed at {1}.<br>"
+                + "Please upload again with a new or an existing open changeset.</html>",
+                changsetId, new SimpleDateFormat().format(d)
+        );
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                msg,
+                tr("Changeset closed"),
+                JOptionPane.ERROR_MESSAGE
+        );
+    }
+
+    /**
+     * Handles the case where deleting a node failed because it is still in use in
+     * a non-deleted way on the server.
+     */
+    protected void handleUploadConflictForNodeStillInUse(long nodeId, long wayId) {
+        ButtonSpec[] options = new ButtonSpec[] {
+                new ButtonSpec(
+                        tr("Prepare conflict resolution"),
+                        ImageProvider.get("ok"),
+                        tr("Click to download all parent ways for node {0}", nodeId),
+                        null /* no specific help context */
+                ),
+                new ButtonSpec(
+                        tr("Cancel"),
+                        ImageProvider.get("cancel"),
+                        tr("Click to cancel and to resume editing the map", nodeId),
+                        null /* no specific help context */
+                )
+        };
+        String msg =  tr("<html>Uploading <strong>failed</strong> because you tried "
+                + "to delete node {0} which is still in use in way {1}.<br><br>"
+                + "Click <strong>{2}</strong> to download all parent ways of node {0}.<br>"
+                + "If necessary JOSM will create conflicts which you can resolve in the Conflict Resolution Dialog."
+                + "</html>",
+                nodeId, wayId, options[0].text
+        );
+
+        int ret = HelpAwareOptionPane.showOptionDialog(
+                Main.parent,
+                msg,
+                tr("Node still in use"),
+                JOptionPane.ERROR_MESSAGE,
+                null,
+                options,
+                options[0],
+                "/Action/Upload#NodeStillInUseInWay"
+        );
+        if (ret != 0) return;
+        DownloadReferrersAction.downloadReferrers(Main.map.mapView.getEditLayer(), nodeId, OsmPrimitiveType.NODE);
+    }
+
+    /**
+     * handles an upload conflict, i.e. an error indicated by a HTTP return code 409.
+     *
+     * @param e  the exception
+     */
+    protected void handleUploadConflict(OsmApiException e) {
+        String pattern = "Version mismatch: Provided (\\d+), server had: (\\d+) of (\\S+) (\\d+)";
+        Pattern p = Pattern.compile(pattern);
+        Matcher m = p.matcher(e.getErrorHeader());
+        if (m.matches()) {
+            handleUploadConflictForKnownConflict(OsmPrimitiveType.from(m.group(3)), Long.parseLong(m.group(4)), m.group(2),m.group(1));
+            return;
+        }
+        pattern ="The changeset (\\d+) was closed at (.*)";
+        p = Pattern.compile(pattern);
+        m = p.matcher(e.getErrorHeader());
+        if (m.matches()) {
+            handleUploadConflictForClosedChangeset(Long.parseLong(m.group(1)), DateUtils.fromString(m.group(2)));
+            return;
+        }
+        pattern = "Node (\\d+) is still used by way (\\d+).";
+        p = Pattern.compile(pattern);
+        m = p.matcher(e.getErrorHeader());
+        if (m.matches()) {
+            handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2)));
+            return;
+        }
+        logger.warning(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader()));
+        handleUploadConflictForUnknownConflict();
+    }
+
+    /**
+     * handles an precondition failed conflict, i.e. an error indicated by a HTTP return code 412.
+     *
+     * @param e  the exception
+     */
+    protected void handlePreconditionFailed(OsmApiException e) {
+        String pattern = "Precondition failed: Node (\\d+) is still used by way (\\d+).";
+        Pattern p = Pattern.compile(pattern);
+        Matcher m = p.matcher(e.getErrorHeader());
+        if (m.matches()) {
+            handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2)));
+            return;
+        }
+        logger.warning(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader()));
+        ExceptionDialogUtil.explainPreconditionFailed(e);
+    }
+
+    /**
+     * Handles an error which is caused by a delete request for an already deleted
+     * {@see OsmPrimitive} on the server, i.e. a HTTP response code of 410.
+     * Note that an <strong>update</strong> on an already deleted object results
+     * in a 409, not a 410.
+     *
+     * @param e the exception
+     */
+    protected void handleGone(OsmApiPrimitiveGoneException e) {
+        if (e.isKnownPrimitive()) {
+            new UpdateSelectionAction().handlePrimitiveGoneException(e.getPrimitiveId(),e.getPrimitiveType());
+        } else {
+            ExceptionDialogUtil.explainGoneForUnknownPrimitive(e);
+        }
+    }
+
+    /**
+     * error handler for any exception thrown during upload
+     *
+     * @param e the exception
+     */
+    protected void handleFailedUpload(Exception e) {
+        // API initialization failed. Notify the user and return.
+        //
+        if (e instanceof OsmApiInitializationException) {
+            ExceptionDialogUtil.explainOsmApiInitializationException((OsmApiInitializationException)e);
+            return;
+        }
+
+        if (e instanceof OsmApiPrimitiveGoneException) {
+            handleGone((OsmApiPrimitiveGoneException)e);
+            return;
+        }
+        if (e instanceof OsmApiException) {
+            OsmApiException ex = (OsmApiException)e;
+            // There was an upload conflict. Let the user decide whether
+            // and how to resolve it
+            //
+            if(ex.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {
+                handleUploadConflict(ex);
+                return;
+            }
+            // There was a precondition failed. Notify the user.
+            //
+            else if (ex.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) {
+                handlePreconditionFailed(ex);
+                return;
+            }
+            // Tried to update or delete a primitive which never existed on
+            // the server?
+            //
+            else if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+                ExceptionDialogUtil.explainNotFound(ex);
+                return;
+            }
+        }
+
+        ExceptionDialogUtil.explainException(e);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java	(revision 2599)
@@ -0,0 +1,171 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * BasicUploadSettingsPanel allows to enter the basic parameters required for uploading
+ * data.
+ *
+ */
+public class BasicUploadSettingsPanel extends JPanel implements PropertyChangeListener{
+    static public final String UPLOAD_COMMENT_PROP = BasicUploadSettingsPanel.class.getName() + ".uploadComment";
+    public static final String HISTORY_KEY = "upload.comment.history";
+
+    /** the history combo box for the upload comment */
+    private HistoryComboBox hcbUploadComment;
+    /** the panel with a summary of the upload parameters */
+    private UploadParameterSummaryPanel pnlUploadParameterSummary;
+
+    protected JPanel buildUploadCommentPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        pnl.add(new JLabel(tr("Provide a brief comment for the changes you are uploading:")), GBC.eol().insets(0, 5, 10, 3));
+        hcbUploadComment = new HistoryComboBox();
+        hcbUploadComment.setToolTipText(tr("Enter an upload comment (min. 3 characters)"));
+        List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(HISTORY_KEY, new LinkedList<String>()));
+        // we have to reverse the history, because ComboBoxHistory will reverse it again
+        // in addElement()
+        //
+        Collections.reverse(cmtHistory);
+        hcbUploadComment.setPossibleItems(cmtHistory);
+        hcbUploadComment.getEditor().addActionListener(
+                new ActionListener() {
+                    public void actionPerformed(ActionEvent e) {
+                        firePropertyChange(UPLOAD_COMMENT_PROP, null, hcbUploadComment.getText());
+                    }
+                }
+        );
+        hcbUploadComment.getEditor().getEditorComponent().addFocusListener(
+                new FocusAdapter() {
+                    @Override
+                    public void focusLost(FocusEvent e) {
+                        firePropertyChange(UPLOAD_COMMENT_PROP, null, hcbUploadComment.getText());
+                    }
+                }
+        );
+        pnl.add(hcbUploadComment, GBC.eol().fill(GBC.HORIZONTAL));
+        return pnl;
+    }
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
+        add(buildUploadCommentPanel(), BorderLayout.NORTH);
+        add(pnlUploadParameterSummary = new UploadParameterSummaryPanel(), BorderLayout.CENTER);
+    }
+
+    public BasicUploadSettingsPanel() {
+        build();
+    }
+
+    public void setUploadCommentDownFocusTraversalHandler(final Action handler) {
+        hcbUploadComment.getEditor().addActionListener(handler);
+        hcbUploadComment.getEditor().getEditorComponent().addKeyListener(
+                new KeyListener() {
+                    public void keyTyped(KeyEvent e) {
+                        if (e.getKeyCode() == KeyEvent.VK_TAB) {
+                            handler.actionPerformed(new ActionEvent(hcbUploadComment,0, "focusDown"));
+                        }
+                    }
+                    public void keyReleased(KeyEvent e) {}
+
+                    public void keyPressed(KeyEvent e) {}
+                }
+        );
+    }
+
+    /**
+     * Remembers the user input in the preference settings
+     */
+    public void rememberUserInput() {
+        // store the history of comments
+        hcbUploadComment.addCurrentItemToHistory();
+        Main.pref.putCollection(HISTORY_KEY, hcbUploadComment.getHistory());
+    }
+
+    /**
+     * Initializes the panel for user input
+     */
+    public void startUserInput() {
+        List<String> history = hcbUploadComment.getHistory();
+        if (history != null && !history.isEmpty()) {
+            hcbUploadComment.setText(history.get(0));
+        }
+        hcbUploadComment.requestFocusInWindow();
+        hcbUploadComment.getEditor().getEditorComponent().requestFocusInWindow();
+    }
+
+    /**
+     * Replies the current upload comment
+     *
+     * @return
+     */
+    public String getUploadComment() {
+        return hcbUploadComment.getText();
+    }
+
+    /**
+     * Sets the current upload comment
+     *
+     * @return
+     */
+    public void setUploadComment(String uploadComment) {
+        if (uploadComment == null) {
+            uploadComment = "";
+        }
+        if (!uploadComment.equals(hcbUploadComment.getText())) {
+            hcbUploadComment.setText(uploadComment);
+        }
+    }
+
+    public void initEditingOfUploadComment(String comment) {
+        setUploadComment(comment);
+        hcbUploadComment.getEditor().selectAll();
+        hcbUploadComment.requestFocusInWindow();
+    }
+
+
+    public UploadParameterSummaryPanel getUploadParameterSummaryPanel() {
+        return pnlUploadParameterSummary;
+    }
+
+    /* -------------------------------------------------------------------------- */
+    /* Interface PropertyChangeListener                                           */
+    /* -------------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(TagSettingsPanel.UPLOAD_COMMENT_PROP)) {
+            String comment = (String)evt.getNewValue();
+            if (comment == null) {
+                comment = "";
+            }
+            if (comment.equals(hcbUploadComment.getText()))
+                // nothing to change - return
+                return;
+            hcbUploadComment.setText(comment);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/ChangesetManagementPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/ChangesetManagementPanel.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/ChangesetManagementPanel.java	(revision 2599)
@@ -0,0 +1,346 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Collections;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * ChangesetManagementPanel allows to configure changeset to be used in the next
+ * upload.
+ * 
+ * It is displayed as one of the configuration panels in the {@see UploadDialog}.
+ * 
+ * ChangesetManagementPanel is a source for {@see PropertyChangeEvent}s. Clients can listen
+ * to
+ * <ul>
+ *   <li>{@see #SELECTED_CHANGESET_PROP}  - the new value in the property change event is
+ *   the changeset selected by the user. The value is null if the user didn't select a
+ *   a changeset or if he chosed to use a new changeset.</li>
+ *   <li> {@see #CLOSE_CHANGESET_AFTER_UPLOAD} - the new value is a boolean value indicating
+ *   whether the changeset should be closed after the next upload</li>
+ * </ul>
+ */
+public class ChangesetManagementPanel extends JPanel implements ListDataListener {
+    public final static String SELECTED_CHANGESET_PROP = ChangesetManagementPanel.class.getName() + ".selectedChangeset";
+    public final static String CLOSE_CHANGESET_AFTER_UPLOAD = ChangesetManagementPanel.class.getName() + ".closeChangesetAfterUpload";
+
+    private ButtonGroup bgUseNewOrExisting;
+    private JRadioButton rbUseNew;
+    private JRadioButton rbExisting;
+    private JComboBox cbOpenChangesets;
+    private JButton btnRefresh;
+    private JButton btnClose;
+    private JCheckBox cbCloseAfterUpload;
+    private OpenChangesetComboBoxModel model;
+
+    /**
+     * builds the GUI
+     */
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+        setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
+
+        bgUseNewOrExisting = new ButtonGroup();
+
+        gc.gridwidth = 4;
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(0, 0, 5, 0);
+        add(new JMultilineLabel("Please decide what changeset data is uploaded to an whether to close the changeset after the next upload."), gc);
+
+        gc.gridwidth = 4;
+        gc.gridy = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(0,0,0,0);
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        rbUseNew = new JRadioButton(tr("Upload to a new changeset"));
+        rbUseNew.setToolTipText(tr("Open a new changeset and use it in the next upload"));
+        bgUseNewOrExisting.add(rbUseNew);
+        add(rbUseNew, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.gridwidth = 1;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        rbExisting = new JRadioButton(tr("Upload to an existing changeset"));
+        rbExisting.setToolTipText(tr("Upload data to an already existing and open changeset"));
+        bgUseNewOrExisting.add(rbExisting);
+        add(rbExisting, gc);
+
+        gc.gridx = 1;
+        gc.gridy = 2;
+        gc.gridwidth = 1;
+        gc.weightx = 1.0;
+        model = new OpenChangesetComboBoxModel();
+        cbOpenChangesets = new JComboBox(model);
+        cbOpenChangesets.setToolTipText("Select an open changeset");
+        cbOpenChangesets.setRenderer(new ChangesetCellRenderer());
+        cbOpenChangesets.addItemListener(new ChangesetListItemStateListener());
+        Dimension d = cbOpenChangesets.getPreferredSize();
+        d.width = 200;
+        cbOpenChangesets.setPreferredSize(d);
+        d.width = 100;
+        cbOpenChangesets.setMinimumSize(d);
+        model.addListDataListener(this);
+        add(cbOpenChangesets, gc);
+
+        gc.gridx = 2;
+        gc.gridy = 2;
+        gc.weightx = 0.0;
+        gc.gridwidth = 1;
+        gc.weightx = 0.0;
+        btnRefresh = new JButton(new RefreshAction());
+        btnRefresh.setMargin(new Insets(0,0,0,0));
+        add(btnRefresh, gc);
+
+        gc.gridx = 3;
+        gc.gridy = 2;
+        gc.gridwidth = 1;
+        CloseChangesetAction closeChangesetAction = new CloseChangesetAction();
+        btnClose = new JButton(closeChangesetAction);
+        btnClose.setMargin(new Insets(0,0,0,0));
+        cbOpenChangesets.addItemListener(closeChangesetAction);
+        add(btnClose, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 3;
+        gc.gridwidth = 4;
+        gc.weightx = 1.0;
+        cbCloseAfterUpload = new JCheckBox(tr("Close changeset after upload"));
+        cbCloseAfterUpload.setToolTipText(tr("Select to close the changeset after the next upload"));
+        add(cbCloseAfterUpload, gc);
+        cbCloseAfterUpload.setSelected(true);
+        cbCloseAfterUpload.addItemListener(new CloseAfterUploadItemStateListener());
+
+        gc.gridx = 0;
+        gc.gridy = 5;
+        gc.gridwidth = 4;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        gc.fill = GridBagConstraints.BOTH;
+        add(new JPanel(), gc);
+
+        rbUseNew.getModel().addItemListener(new RadioButtonHandler());
+        rbExisting.getModel().addItemListener(new RadioButtonHandler());
+    }
+
+    public ChangesetManagementPanel() {
+        build();
+        refreshGUI();
+    }
+
+    protected void refreshGUI() {
+        rbExisting.setEnabled(model.getSize() > 0);
+        if (model.getSize() == 0) {
+            if (!rbUseNew.isSelected()) {
+                rbUseNew.setSelected(true);
+            }
+        }
+        cbOpenChangesets.setEnabled(model.getSize() > 0 && rbExisting.isSelected());
+    }
+
+    /**
+     * Replies the currently selected changeset. null, if no changeset is
+     * selected or if the user has chosen to use a new changeset.
+     * 
+     * @return the currently selected changeset. null, if no changeset is
+     * selected.
+     */
+    public Changeset getSelectedChangeset() {
+        if (rbUseNew.isSelected())
+            return null;
+        return (Changeset)cbOpenChangesets.getSelectedItem();
+    }
+
+    /**
+     * Replies true if the user has chosen to close the changeset after the
+     * next upload
+     * 
+     */
+    public boolean isCloseChangesetAfterUpload() {
+        return cbCloseAfterUpload.isSelected();
+    }
+
+    /**
+     * Replies the default value for "created_by"
+     *
+     * @return the default value for "created_by"
+     */
+    protected String getDefaultCreatedBy() {
+        Object ua = System.getProperties().get("http.agent");
+        return(ua == null) ? "JOSM" : ua.toString();
+    }
+
+    public void updateListOfChangesetsAfterUploadOperation(Changeset cs) {
+        if (cs == null || cs.isNew()) {
+            model.setSelectedItem(null);
+        } else if (cs.isOpen()){
+            if (cs.get("created_by") == null) {
+                cs.put("created_by", getDefaultCreatedBy());
+            }
+            model.addOrUpdate(cs);
+            cs = model.getChangesetById(cs.getId());
+            model.setSelectedItem(cs);
+            rbExisting.setSelected(true);
+        } else if (!cs.isOpen()){
+            removeChangeset(cs);
+            rbUseNew.setSelected(true);
+        }
+    }
+
+    /**
+     * Remove a changeset from the list of open changeset
+     *
+     * @param cs the changeset to be removed. Ignored if null.
+     */
+    public void removeChangeset(Changeset cs) {
+        if (cs ==  null) return;
+        model.removeChangeset(cs);
+        refreshGUI();
+    }
+
+    /* ---------------------------------------------------------------------------- */
+    /* Interface ListDataListener                                                   */
+    /* ---------------------------------------------------------------------------- */
+    public void contentsChanged(ListDataEvent e) {
+        refreshGUI();
+    }
+
+    public void intervalAdded(ListDataEvent e) {
+        refreshGUI();
+    }
+
+    public void intervalRemoved(ListDataEvent e) {
+        refreshGUI();
+    }
+
+    /**
+     * Listens to changes in the selected changeset and accordingly fires property
+     * change events.
+     *
+     */
+    class ChangesetListItemStateListener implements ItemListener {
+        public void itemStateChanged(ItemEvent e) {
+            Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
+            if (rbExisting.isSelected()) {
+                firePropertyChange(SELECTED_CHANGESET_PROP, null, cs);
+            }
+        }
+    }
+
+    /**
+     * Listens to changes in "close after upload" flag and fires
+     * property change events.
+     *
+     */
+    class CloseAfterUploadItemStateListener implements ItemListener {
+        public void itemStateChanged(ItemEvent e) {
+            if (e.getItemSelectable() != cbCloseAfterUpload)
+                return;
+            switch(e.getStateChange()) {
+            case ItemEvent.SELECTED:
+                firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, false, true);
+                break;
+            case ItemEvent.DESELECTED:
+                firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, true, false);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Listens to changes in the two radio buttons rbUseNew and rbUseExisting.
+     *
+     */
+    class RadioButtonHandler implements ItemListener {
+        public void itemStateChanged(ItemEvent e) {
+            if (rbUseNew.isSelected()) {
+                cbOpenChangesets.setEnabled(false);
+                firePropertyChange(SELECTED_CHANGESET_PROP, null, null);
+            } else {
+                cbOpenChangesets.setEnabled(true);
+                if (cbOpenChangesets.getSelectedItem() == null) {
+                    model.selectFirstChangeset();
+                }
+                Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
+                firePropertyChange(SELECTED_CHANGESET_PROP, null, cs);
+            }
+        }
+    }
+
+    /**
+     * Refreshes the list of open changesets
+     *
+     */
+    class RefreshAction extends AbstractAction {
+        public RefreshAction() {
+            //putValue(NAME, tr("Reload"));
+            putValue(SHORT_DESCRIPTION, tr("Load the list of your open changesets from the server"));
+            putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(model);
+            Main.worker.submit(task);
+        }
+    }
+
+    /**
+     * Closes the currently selected changeset
+     *
+     */
+    class CloseChangesetAction extends AbstractAction implements ItemListener{
+        public CloseChangesetAction() {
+            //putValue(NAME, tr("Close"));
+            putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
+            putValue(SHORT_DESCRIPTION, tr("Close the currently selected open changeset"));
+            refreshEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
+            if (cs == null) return;
+            CloseChangesetTask task = new CloseChangesetTask(Collections.singletonList(cs));
+            Main.worker.submit(task);
+        }
+
+        protected void refreshEnabledState() {
+            setEnabled(cbOpenChangesets.getModel().getSize() > 0 && cbOpenChangesets.getSelectedItem() != null);
+        }
+
+        public void itemStateChanged(ItemEvent e) {
+            refreshEnabledState();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/CloseChangesetTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/CloseChangesetTask.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/CloseChangesetTask.java	(revision 2599)
@@ -60,5 +60,5 @@
                     public void run() {
                         for (Changeset cs: closedChangesets) {
-                            UploadDialog.getUploadDialog().setOrUpdateChangeset(cs);
+                            UploadDialog.getUploadDialog().updateListOfChangesetsAfterUploadOperation(cs);
                         }
                     }
Index: /trunk/src/org/openstreetmap/josm/gui/io/ConfigurationParameterRequestHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/ConfigurationParameterRequestHandler.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/ConfigurationParameterRequestHandler.java	(revision 2599)
@@ -0,0 +1,7 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+public interface ConfigurationParameterRequestHandler {
+    void handleChangesetConfigurationRequest();
+    void handleUploadStrategyConfigurationRequest();
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java	(revision 2599)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.io.IOException;
@@ -13,5 +15,4 @@
 import org.openstreetmap.josm.gui.ExceptionDialogUtil;
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.gui.io.UploadDialog.OpenChangesetModel;
 import org.openstreetmap.josm.io.ChangesetQuery;
 import org.openstreetmap.josm.io.OsmServerChangesetReader;
@@ -19,5 +20,4 @@
 import org.openstreetmap.josm.io.OsmTransferException;
 import org.xml.sax.SAXException;
-import static org.openstreetmap.josm.tools.I18n.tr;
 
 /**
@@ -31,5 +31,5 @@
     private OsmServerChangesetReader reader;
     private List<Changeset> changesets;
-    private OpenChangesetModel model;
+    private OpenChangesetComboBoxModel model;
     private Exception lastException;
     private UserInfo userInfo;
@@ -40,5 +40,5 @@
      * after download
      */
-    public DownloadOpenChangesetsTask(OpenChangesetModel model) {
+    public DownloadOpenChangesetsTask(OpenChangesetComboBoxModel model) {
         super(tr("Downloading open changesets ...", false /* don't ignore exceptions */));
         this.model = model;
Index: /trunk/src/org/openstreetmap/josm/gui/io/MaxChangesetSizeExceededPolicy.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/MaxChangesetSizeExceededPolicy.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/MaxChangesetSizeExceededPolicy.java	(revision 2599)
@@ -0,0 +1,21 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+public enum MaxChangesetSizeExceededPolicy {
+    /**
+     * Abort uploading. Send the user back to map editing.
+     */
+    ABORT,
+    /**
+     * Fill one changeset. If it is full send the user back to the
+     * upload dialog where he can choose another changeset or another
+     * upload strategy if he or she wants to.
+     */
+    FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG,
+
+    /**
+     * Automatically open as many new changesets as necessary to upload
+     * the data.
+     */
+    AUTOMATICALLY_OPEN_NEW_CHANGESETS
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java	(revision 2599)
@@ -0,0 +1,143 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.DefaultComboBoxModel;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+
+/**
+ * A combobox model for the list of open changesets
+ *
+ */
+public class OpenChangesetComboBoxModel extends DefaultComboBoxModel {
+    private List<Changeset> changesets;
+    private long uid;
+    private Changeset selectedChangeset = null;
+
+    protected Changeset getChangesetById(long id) {
+        for (Changeset cs : changesets) {
+            if (cs.getId() == id) return cs;
+        }
+        return null;
+    }
+
+    public OpenChangesetComboBoxModel() {
+        this.changesets = new ArrayList<Changeset>();
+    }
+
+    protected void internalAddOrUpdate(Changeset cs) {
+        Changeset other = getChangesetById(cs.getId());
+        if (other != null) {
+            cs.cloneFrom(other);
+        } else {
+            changesets.add(cs);
+        }
+    }
+
+    public void addOrUpdate(Changeset cs) {
+        if (cs.getId() <= 0 )
+            throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", cs.getId()));
+        internalAddOrUpdate(cs);
+        fireContentsChanged(this, 0, getSize());
+    }
+
+    public void remove(long id) {
+        Changeset cs = getChangesetById(id);
+        if (cs != null) {
+            changesets.remove(cs);
+        }
+        fireContentsChanged(this, 0, getSize());
+    }
+
+    public void setChangesets(Collection<Changeset> changesets) {
+        this.changesets.clear();
+        if (changesets != null) {
+            for (Changeset cs: changesets) {
+                internalAddOrUpdate(cs);
+            }
+        }
+        fireContentsChanged(this, 0, getSize());
+        if (getSelectedItem() == null && !this.changesets.isEmpty()) {
+            setSelectedItem(this.changesets.get(0));
+        } else if (getSelectedItem() != null) {
+            if (changesets.contains(getSelectedItem())) {
+                setSelectedItem(getSelectedItem());
+            } else if (!this.changesets.isEmpty()){
+                setSelectedItem(this.changesets.get(0));
+            } else {
+                setSelectedItem(null);
+            }
+        } else {
+            setSelectedItem(null);
+        }
+    }
+
+    public void setUserId(long uid) {
+        this.uid = uid;
+    }
+
+    public long getUserId() {
+        return uid;
+    }
+
+    public void selectFirstChangeset() {
+        if (changesets == null || changesets.isEmpty()) {
+            setSelectedItem(null);
+        } else {
+            setSelectedItem(changesets.get(0));
+        }
+    }
+
+    public void removeChangeset(Changeset cs) {
+        if (cs == null) return;
+        changesets.remove(cs);
+        if (selectedChangeset == cs) {
+            selectFirstChangeset();
+        }
+        fireContentsChanged(this, 0, getSize());
+    }
+    /* ------------------------------------------------------------------------------------ */
+    /* ComboBoxModel                                                                        */
+    /* ------------------------------------------------------------------------------------ */
+    @Override
+    public Object getElementAt(int index) {
+        return changesets.get(index);
+    }
+
+    @Override
+    public int getIndexOf(Object anObject) {
+        return changesets.indexOf(anObject);
+    }
+
+    @Override
+    public int getSize() {
+        return changesets.size();
+    }
+
+    @Override
+    public Object getSelectedItem() {
+        return selectedChangeset;
+    }
+
+    @Override
+    public void setSelectedItem(Object anObject) {
+        if (anObject == null) {
+            this.selectedChangeset = null;
+            super.setSelectedItem(null);
+            return;
+        }
+        if (! (anObject instanceof Changeset)) return;
+        Changeset cs = (Changeset)anObject;
+        if (cs.getId() == 0 || ! cs.isOpen()) return;
+        Changeset candidate = getChangesetById(cs.getId());
+        if (candidate == null) return;
+        this.selectedChangeset = candidate;
+        super.setSelectedItem(selectedChangeset);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 2599)
@@ -33,4 +33,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.data.APIDataSet;
 import org.openstreetmap.josm.gui.ExceptionDialogUtil;
 import org.openstreetmap.josm.gui.SideButton;
@@ -406,4 +407,12 @@
                     continue;
                 }
+                final UploadDialog dialog = UploadDialog.getUploadDialog();
+                dialog.setUploadedPrimitives(new APIDataSet(layerInfo.getLayer().data));
+                dialog.setVisible(true);
+                if (dialog.isCanceled()) {
+                    model.setUploadState(layerInfo.getLayer(), UploadOrSaveState.CANCELLED);
+                    continue;
+                }
+                dialog.rememberUserInput();
 
                 currentTask = new UploadLayerTask(
@@ -411,6 +420,5 @@
                         layerInfo.getLayer(),
                         monitor,
-                        UploadDialog.getUploadDialog().getChangeset(),
-                        UploadDialog.getUploadDialog().isDoCloseAfterUpload()
+                        UploadDialog.getUploadDialog().getChangeset()
                 );
                 currentFuture = worker.submit(currentTask);
Index: /trunk/src/org/openstreetmap/josm/gui/io/TagSettingsPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/TagSettingsPanel.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/TagSettingsPanel.java	(revision 2599)
@@ -0,0 +1,132 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+import javax.swing.JPanel;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
+import org.openstreetmap.josm.gui.tagging.TagModel;
+
+public class TagSettingsPanel extends JPanel implements PropertyChangeListener, TableModelListener {
+    static public final String UPLOAD_COMMENT_PROP = TagSettingsPanel.class.getName() + ".uploadComment";
+
+    /** checkbox for selecting whether an atomic upload is to be used  */
+    private TagEditorPanel pnlTagEditor;
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        add(pnlTagEditor = new TagEditorPanel(), BorderLayout.CENTER);
+    }
+
+    public TagSettingsPanel() {
+        build();
+        pnlTagEditor.getModel().addTableModelListener(this);
+    }
+
+    /**
+     * Replies the default value for "created_by"
+     *
+     * @return the default value for "created_by"
+     */
+    protected String getDefaultCreatedBy() {
+        Object ua = System.getProperties().get("http.agent");
+        return(ua == null) ? "JOSM" : ua.toString();
+    }
+
+    public void setUploadComment(String comment) {
+        if (comment == null) {
+            comment = "";
+        }
+        comment  = comment.trim();
+        String commentInTag = getUploadComment();
+        if (comment.equals(commentInTag))
+            return;
+
+        if (comment.equals("")) {
+            pnlTagEditor.getModel().delete("comment");
+            return;
+        }
+        TagModel tag = pnlTagEditor.getModel().get("comment");
+        if (tag == null) {
+            tag = new TagModel("comment", comment);
+            pnlTagEditor.getModel().add(tag);
+        } else {
+            pnlTagEditor.getModel().updateTagValue(tag, comment);
+        }
+    }
+
+    protected String getUploadComment() {
+        TagModel tag = pnlTagEditor.getModel().get("comment");
+        if (tag == null) return null;
+        return tag.getValue();
+    }
+
+    protected void initNewChangeset() {
+        String currentComment = getUploadComment();
+        pnlTagEditor.getModel().clear();
+        if (currentComment != null) {
+            pnlTagEditor.getModel().add("comment", currentComment);
+        }
+        pnlTagEditor.getModel().add("created_by", getDefaultCreatedBy());
+    }
+
+    protected void initFromExistingChangeset(Changeset cs) {
+        String currentComment = getUploadComment();
+        Map<String,String> tags = cs.getKeys();
+        if (tags.get("comment") == null) {
+            tags.put("comment", currentComment);
+        }
+        tags.put("created_by", getDefaultCreatedBy());
+        pnlTagEditor.getModel().initFromTags(tags);
+    }
+
+    public void initFromChangeset(Changeset cs) {
+        if (cs == null) {
+            initNewChangeset();
+        } else {
+            initFromExistingChangeset(cs);
+        }
+    }
+
+    /**
+     * Replies the map with the current tags in the tag editor model.
+     * 
+     * @return the map with the current tags in the tag editor model.
+     */
+    public Map<String,String> getTags() {
+        return pnlTagEditor.getModel().getTags();
+    }
+
+    public void startUserInput() {
+        pnlTagEditor.initAutoCompletion(Main.main.getEditLayer());
+    }
+
+    /* -------------------------------------------------------------------------- */
+    /* Interface PropertyChangeListener                                           */
+    /* -------------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
+            Changeset cs = (Changeset)evt.getNewValue();
+            initFromChangeset(cs);
+        } else if (evt.getPropertyName().equals(BasicUploadSettingsPanel.UPLOAD_COMMENT_PROP)) {
+            String comment = (String)evt.getNewValue();
+            setUploadComment(comment);
+        }
+    }
+
+    /* -------------------------------------------------------------------------- */
+    /* Interface TableChangeListener                                              */
+    /* -------------------------------------------------------------------------- */
+    public void tableChanged(TableModelEvent e) {
+        String uploadComment = getUploadComment();
+        firePropertyChange(UPLOAD_COMMENT_PROP, null, uploadComment);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java	(revision 2599)
@@ -0,0 +1,182 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.logging.Logger;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSetMerger;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
+import org.openstreetmap.josm.io.OsmServerObjectReader;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.xml.sax.SAXException;
+
+/**
+ * The asynchronous task for updating a collection of objects using multi fetch.
+ *
+ */
+public class UpdatePrimitivesTask extends PleaseWaitRunnable {
+    static private final Logger logger = Logger.getLogger(UpdatePrimitivesTask.class.getName());
+
+    private DataSet ds;
+    private boolean canceled;
+    private Exception lastException;
+    private Collection<? extends OsmPrimitive> toUpdate;
+    private OsmDataLayer layer;
+    private MultiFetchServerObjectReader multiObjectReader;
+    private OsmServerObjectReader objectReader;
+
+    /**
+     * Creates the  task
+     * 
+     * @param layer the layer in which primitives are updated. Must not be null.
+     * @param toUpdate a collection of primitives to update from the server. Set to
+     * the empty collection if null.
+     * @throws IllegalArgumentException thrown if layer is null.
+     */
+    public UpdatePrimitivesTask(OsmDataLayer layer, Collection<? extends OsmPrimitive> toUpdate) throws IllegalArgumentException{
+        super(tr("Update objects"), false /* don't ignore exception */);
+        ensureParameterNotNull(layer, "layer");
+        if (toUpdate == null) {
+            toUpdate = Collections.emptyList();
+        }
+        this.layer = layer;
+        this.toUpdate = toUpdate;
+    }
+
+    @Override
+    protected void cancel() {
+        canceled = true;
+        synchronized(this) {
+            if (multiObjectReader != null) {
+                multiObjectReader.cancel();
+            }
+            if (objectReader != null) {
+                objectReader.cancel();
+            }
+        }
+    }
+
+    @Override
+    protected void finish() {
+        if (canceled)
+            return;
+        if (lastException != null) {
+            ExceptionDialogUtil.explainException(lastException);
+            return;
+        }
+        Runnable r = new Runnable() {
+            public void run() {
+                layer.mergeFrom(ds);
+                layer.onPostDownloadFromServer();
+            }
+        };
+
+        if (SwingUtilities.isEventDispatchThread()) {
+            r.run();
+        } else {
+            try {
+                SwingUtilities.invokeAndWait(r);
+            } catch(InterruptedException e) {
+                e.printStackTrace();
+            } catch(InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    protected void initMultiFetchReaderWithNodes(MultiFetchServerObjectReader reader) {
+        getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to update ..."));
+        for (OsmPrimitive primitive : toUpdate) {
+            if (primitive instanceof Node && !primitive.isNew()) {
+                reader.append((Node)primitive);
+            } else if (primitive instanceof Way) {
+                Way way = (Way)primitive;
+                for (Node node: way.getNodes()) {
+                    if (!node.isNew()) {
+                        reader.append(node);
+                    }
+                }
+            }
+        }
+    }
+
+    protected void initMultiFetchReaderWithWays(MultiFetchServerObjectReader reader) {
+        getProgressMonitor().indeterminateSubTask(tr("Initializing ways to update ..."));
+        for (OsmPrimitive primitive : toUpdate) {
+            if (primitive instanceof Way && !primitive.isNew()) {
+                reader.append((Way)primitive);
+            }
+        }
+    }
+
+    protected void initMultiFetchReaderWithRelations(MultiFetchServerObjectReader reader) {
+        getProgressMonitor().indeterminateSubTask(tr("Initializing relations to update ..."));
+        for (OsmPrimitive primitive : toUpdate) {
+            if (primitive instanceof Relation && !primitive.isNew()) {
+                reader.append((Relation)primitive);
+            }
+        }
+    }
+
+    @Override
+    protected void realRun() throws SAXException, IOException, OsmTransferException {
+        this.ds = new DataSet();
+        DataSet theirDataSet;
+        try {
+            synchronized(this) {
+                if (canceled) return;
+                multiObjectReader = new MultiFetchServerObjectReader();
+            }
+            initMultiFetchReaderWithNodes(multiObjectReader);
+            initMultiFetchReaderWithWays(multiObjectReader);
+            initMultiFetchReaderWithRelations(multiObjectReader);
+            theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
+            synchronized(this) {
+                multiObjectReader = null;
+            }
+            DataSetMerger merger = new DataSetMerger(ds, theirDataSet);
+            merger.merge();
+            // a way loaded with MultiFetch may be incomplete because at least one of its
+            // nodes isn't present in the local data set. We therefore fully load all
+            // incomplete ways.
+            //
+            for (Way w : ds.getWays()) {
+                if (canceled) return;
+                if (w.isIncomplete()) {
+                    synchronized(this) {
+                        if (canceled) return;
+                        objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
+                    }
+                    theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
+                    synchronized (this) {
+                        objectReader = null;
+                    }
+                    merger = new DataSetMerger(ds, theirDataSet);
+                    merger.merge();
+                }
+            }
+        } catch(Exception e) {
+            if (canceled)
+                return;
+            lastException = e;
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 2599)
@@ -1,64 +1,40 @@
-// License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.io;
 
 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
 
 import java.awt.BorderLayout;
 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.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.logging.Logger;
 
 import javax.swing.AbstractAction;
-import javax.swing.AbstractListModel;
 import javax.swing.BorderFactory;
-import javax.swing.ButtonGroup;
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.InputMap;
 import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JList;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JScrollPane;
 import javax.swing.JTabbedPane;
 import javax.swing.KeyStroke;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import javax.swing.event.ListDataEvent;
-import javax.swing.event.ListDataListener;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.APIDataSet;
 import org.openstreetmap.josm.data.osm.Changeset;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
-import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
 import org.openstreetmap.josm.gui.SideButton;
 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
 import org.openstreetmap.josm.gui.help.HelpUtil;
-import org.openstreetmap.josm.gui.tagging.TagEditorModel;
-import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
-import org.openstreetmap.josm.gui.tagging.TagModel;
-import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
-import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.WindowGeometry;
@@ -69,7 +45,6 @@
  *
  */
-public class UploadDialog extends JDialog {
-
-    public static final String HISTORY_KEY = "upload.comment.history";
+public class UploadDialog extends JDialog implements PropertyChangeListener{
+    protected static final Logger logger = Logger.getLogger(UploadDialog.class.getName());
 
     /**  the unique instance of the upload dialog */
@@ -88,40 +63,22 @@
     }
 
-    /** the list with the added primitives */
-    private PrimitiveList lstAdd;
-    private JLabel lblAdd;
-    private JScrollPane spAdd;
-    /** the list with the updated primitives */
-    private PrimitiveList lstUpdate;
-    private JLabel lblUpdate;
-    private JScrollPane spUpdate;
-    /** the list with the deleted primitives */
-    private PrimitiveList lstDelete;
-    private JLabel lblDelete;
-    private JScrollPane spDelete;
-    /** the panel containing the widgets for the lists of primitives */
-    private JPanel pnlLists;
+    /** the panel with the objects to upload */
+    private UploadedObjectsSummaryPanel pnlUploadedObjects;
+    /** the panel to select the changeset used */
+    private ChangesetManagementPanel pnlChangesetManagement;
+
+    private BasicUploadSettingsPanel pnlBasicUploadSettings;
+
+    private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
+
     /** checkbox for selecting whether an atomic upload is to be used  */
-    private TagEditorPanel tagEditorPanel;
+    private TagSettingsPanel pnlTagSettings;
     /** the tabbed pane used below of the list of primitives  */
-    private JTabbedPane southTabbedPane;
+    private JTabbedPane tpConfigPanels;
     /** the upload button */
     private JButton btnUpload;
 
-    private ChangesetSelectionPanel pnlChangesetSelection;
     private boolean canceled = false;
 
-    /**
-     * builds the panel with the lists of primitives
-     *
-     * @return the panel with the lists of primitives
-     */
-    protected JPanel buildListsPanel() {
-        pnlLists = new JPanel();
-        pnlLists.setLayout(new GridBagLayout());
-        // we don't add the lists yet, see setUploadPrimitives()
-        //
-        return pnlLists;
-    }
 
     /**
@@ -133,35 +90,43 @@
         JPanel pnl = new JPanel();
         pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-        pnl.setLayout(new GridBagLayout());
-        GridBagConstraints gc = new GridBagConstraints();
-
-        // first the panel with the list in the upper half
+        pnl.setLayout(new BorderLayout());
+
+        // the panel with the list of uploaded objects
         //
-        gc.fill = GridBagConstraints.BOTH;
-        gc.weightx = 1.0;
-        gc.weighty = 1.0;
-        pnl.add(buildListsPanel(), gc);
+        pnl.add(pnlUploadedObjects = new UploadedObjectsSummaryPanel(), BorderLayout.CENTER);
 
         // a tabbed pane with two configuration panels in the
         // lower half
         //
-        southTabbedPane = new JTabbedPane();
-        southTabbedPane.add(new JPanel());
-        tagEditorPanel = new TagEditorPanel();
-        southTabbedPane.add(tagEditorPanel);
-        southTabbedPane.setComponentAt(0, pnlChangesetSelection = new ChangesetSelectionPanel());
-        southTabbedPane.setTitleAt(0, tr("Settings"));
-        southTabbedPane.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
-        southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
-        southTabbedPane.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
-        southTabbedPane.addChangeListener(new TabbedPaneChangeLister());
-        JPanel pnl1 = new JPanel();
-        pnl1.setLayout(new BorderLayout());
-        pnl1.add(southTabbedPane,BorderLayout.CENTER);
-        gc.fill = GridBagConstraints.HORIZONTAL;
-        gc.gridy = 1;
-        gc.weightx = 1.0;
-        gc.weighty = 0.0;
-        pnl.add(pnl1, gc);
+        tpConfigPanels = new JTabbedPane() {
+            @Override
+            public Dimension getPreferredSize() {
+                // make sure the tabbed pane never grabs more space than necessary
+                //
+                return super.getMinimumSize();
+            }
+        };
+        tpConfigPanels.add(new JPanel());
+        tpConfigPanels.add(new JPanel());
+        tpConfigPanels.add(new JPanel());
+        tpConfigPanels.add(new JPanel());
+
+        tpConfigPanels.setComponentAt(0, pnlBasicUploadSettings = new BasicUploadSettingsPanel());
+        tpConfigPanels.setTitleAt(0, tr("Settings"));
+        tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
+
+        tpConfigPanels.setComponentAt(1,pnlTagSettings = new TagSettingsPanel());
+        tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
+        tpConfigPanels.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
+
+        tpConfigPanels.setComponentAt(2,pnlChangesetManagement = new ChangesetManagementPanel());
+        tpConfigPanels.setTitleAt(2, tr("Changesets"));
+        tpConfigPanels.setToolTipTextAt(2, tr("Manage open changesets and select a changeset to upload to"));
+
+        tpConfigPanels.setComponentAt(3, pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel());
+        tpConfigPanels.setTitleAt(3, tr("Advanced"));
+        tpConfigPanels.setToolTipTextAt(3, tr("Configure advanced settings"));
+
+        pnl.add(tpConfigPanels, BorderLayout.SOUTH);
         return pnl;
     }
@@ -193,6 +158,6 @@
                 JComponent.WHEN_IN_FOCUSED_WINDOW
         );
-        pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialogs/UploadDialog"))));
-        HelpUtil.setHelpContext(getRootPane(),ht("/Dialogs/UploadDialog"));
+        pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/UploadDialog"))));
+        HelpUtil.setHelpContext(getRootPane(),ht("/Dialog/UploadDialog"));
         return pnl;
     }
@@ -202,5 +167,5 @@
      */
     protected void build() {
-        setTitle(tr("Upload"));
+        setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl()));
         getContentPane().setLayout(new BorderLayout());
         getContentPane().add(buildContentPanel(), BorderLayout.CENTER);
@@ -208,4 +173,52 @@
 
         addWindowListener(new WindowEventHandler());
+
+        // synchronized input of upload comments
+        //
+        //UploadCommentSynchronizer synchronizer = new UploadCommentSynchronizer();
+        //pnlTagSettings.getModeaddTableModelListener(synchronizer);
+        pnlTagSettings.addPropertyChangeListener(pnlBasicUploadSettings);
+        pnlBasicUploadSettings.addPropertyChangeListener(pnlTagSettings);
+
+
+        // make sure the the configuration panels listen to each other
+        // changes
+        //
+        pnlChangesetManagement.addPropertyChangeListener(
+                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
+        );
+        pnlChangesetManagement.addPropertyChangeListener(pnlTagSettings);
+        pnlChangesetManagement.addPropertyChangeListener(this);
+        pnlUploadedObjects.addPropertyChangeListener(
+                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
+        );
+        pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
+        pnlUploadStrategySelectionPanel.addPropertyChangeListener(
+                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
+        );
+
+        // users can click on either of two links in the upload parameter
+        // summary handler. This installs the handler for these two events.
+        // We simply select the appropriate tab in the tabbed pane with the
+        // configuration dialogs.
+        //
+        pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener(
+                new ConfigurationParameterRequestHandler() {
+                    public void handleUploadStrategyConfigurationRequest() {
+                        tpConfigPanels.setSelectedIndex(3);
+                    }
+                    public void handleChangesetConfigurationRequest() {
+                        tpConfigPanels.setSelectedIndex(2);
+                    }
+                }
+        );
+
+        pnlBasicUploadSettings.setUploadCommentDownFocusTraversalHandler(
+                new AbstractAction() {
+                    public void actionPerformed(ActionEvent e) {
+                        btnUpload.requestFocusInWindow();
+                    }
+                }
+        );
     }
 
@@ -215,84 +228,25 @@
     public UploadDialog() {
         super(JOptionPane.getFrameForComponent(Main.parent), true /* modal */);
-        OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
-
-        // initialize the three lists for primitives
-        //
-        lstAdd = new PrimitiveList();
-        lstAdd.setCellRenderer(renderer);
-        lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
-        spAdd = new JScrollPane(lstAdd);
-        lblAdd = new JLabel(tr("Objects to add:"));
-
-        lstUpdate = new PrimitiveList();
-        lstUpdate.setCellRenderer(renderer);
-        lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
-        spUpdate = new JScrollPane(lstUpdate);
-        lblUpdate = new JLabel(tr("Objects to modify:"));
-
-        lstDelete = new PrimitiveList();
-        lstDelete.setCellRenderer(renderer);
-        lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
-        spDelete = new JScrollPane(lstDelete);
-        lblDelete = new JLabel(tr("Objects to delete:"));
-
-        // build the GUI
-        //
         build();
     }
 
     /**
-     * sets the collection of primitives which will be uploaded
-     *
-     * @param add  the collection of primitives to add
-     * @param update the collection of primitives to update
-     * @param delete the collection of primitives to delete
-     */
-    public void setUploadedPrimitives(List<OsmPrimitive> add, List<OsmPrimitive> update, List<OsmPrimitive> delete) {
-        lstAdd.getPrimitiveListModel().setPrimitives(add);
-        lstUpdate.getPrimitiveListModel().setPrimitives(update);
-        lstDelete.getPrimitiveListModel().setPrimitives(delete);
-
-        GridBagConstraints gcLabel = new GridBagConstraints();
-        gcLabel.fill = GridBagConstraints.HORIZONTAL;
-        gcLabel.weightx = 1.0;
-        gcLabel.weighty = 0.0;
-        gcLabel.anchor = GridBagConstraints.FIRST_LINE_START;
-
-        GridBagConstraints gcList = new GridBagConstraints();
-        gcList.fill = GridBagConstraints.BOTH;
-        gcList.weightx = 1.0;
-        gcList.weighty = 1.0;
-        gcList.anchor = GridBagConstraints.CENTER;
-        pnlLists.removeAll();
-        int y = -1;
-        if (!add.isEmpty()) {
-            y++;
-            gcLabel.gridy = y;
-            lblAdd.setText(trn("{0} object to add:", "{0} objects to add:", add.size(),add.size()));
-            pnlLists.add(lblAdd, gcLabel);
-            y++;
-            gcList.gridy = y;
-            pnlLists.add(spAdd, gcList);
-        }
-        if (!update.isEmpty()) {
-            y++;
-            gcLabel.gridy = y;
-            lblUpdate.setText(trn("{0} object to modify:", "{0} objects to modify:", update.size(),update.size()));
-            pnlLists.add(lblUpdate, gcLabel);
-            y++;
-            gcList.gridy = y;
-            pnlLists.add(spUpdate, gcList);
-        }
-        if (!delete.isEmpty()) {
-            y++;
-            gcLabel.gridy = y;
-            lblDelete.setText(trn("{0} object to delete:", "{0} objects to delete:", delete.size(),delete.size()));
-            pnlLists.add(lblDelete, gcLabel);
-            y++;
-            gcList.gridy = y;
-            pnlLists.add(spDelete, gcList);
-        }
-        pnlChangesetSelection.setNumUploadedObjects(add.size() + update.size() + delete.size());
+     * Sets the collection of primitives to upload
+     *
+     * @param toUpload the dataset with the objects to upload. If null, assumes the empty
+     * set of objects to upload
+     * 
+     */
+    public void setUploadedPrimitives(APIDataSet toUpload) {
+        if (toUpload == null) {
+            List<OsmPrimitive> emptyList = Collections.emptyList();
+            pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
+            return;
+        }
+        pnlUploadedObjects.setUploadedPrimitives(
+                toUpload.getPrimitivesToAdd(),
+                toUpload.getPrimitivesToUpdate(),
+                toUpload.getPrimitivesToDelete()
+        );
     }
 
@@ -301,5 +255,6 @@
      */
     public void rememberUserInput() {
-        pnlChangesetSelection.rememberUserInput();
+        pnlBasicUploadSettings.rememberUserInput();
+        pnlUploadStrategySelectionPanel.rememberUserInput();
     }
 
@@ -308,6 +263,14 @@
      */
     public void startUserInput() {
-        tagEditorPanel.initAutoCompletion(Main.main.getEditLayer());
-        pnlChangesetSelection.startUserInput();
+        tpConfigPanels.setSelectedIndex(0);
+        pnlBasicUploadSettings.startUserInput();
+        pnlTagSettings.startUserInput();
+        pnlTagSettings.setUploadComment(getUploadComment());
+        pnlTagSettings.initFromChangeset(pnlChangesetManagement.getSelectedChangeset());
+        pnlUploadStrategySelectionPanel.initFromPreferences();
+        UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
+        pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
+        pnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
+        pnl.setNumObjects(pnlUploadedObjects.getNumObjectsToUpload());
     }
 
@@ -318,7 +281,9 @@
      */
     public Changeset getChangeset() {
-        Changeset cs = pnlChangesetSelection.getChangeset();
-        tagEditorPanel.getModel().applyToPrimitive(cs);
-        cs.put("comment", getUploadComment());
+        Changeset cs = pnlChangesetManagement.getSelectedChangeset();
+        if (cs == null) {
+            cs = new Changeset();
+        }
+        cs.setKeys(pnlTagSettings.getTags());
         return cs;
     }
@@ -330,5 +295,7 @@
      */
     public UploadStrategySpecification getUploadStrategySpecification() {
-        return pnlChangesetSelection.getUploadStrategySpecification();
+        UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification();
+        spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
+        return spec;
     }
 
@@ -343,6 +310,6 @@
      * @param cs the changeset
      */
-    public void setOrUpdateChangeset(Changeset cs) {
-        pnlChangesetSelection.setOrUpdateChangeset(cs);
+    public void updateListOfChangesetsAfterUploadOperation(Changeset cs) {
+        pnlChangesetManagement.updateListOfChangesetsAfterUploadOperation(cs);
     }
 
@@ -355,26 +322,5 @@
     public void removeChangeset(Changeset cs) {
         if (cs == null) return;
-        pnlChangesetSelection.removeChangeset(cs);
-    }
-
-    /**
-     * Replies true if the changeset is to be closed after the
-     * next upload
-     *
-     * @return true if the changeset is to be closed after the
-     * next upload; false, otherwise
-     */
-    public boolean isDoCloseAfterUpload() {
-        return pnlChangesetSelection.isCloseAfterUpload();
-    }
-
-    /**
-     * Replies the default value for "created_by"
-     *
-     * @return the default value for "created_by"
-     */
-    protected String getDefaultCreatedBy() {
-        Object ua = System.getProperties().get("http.agent");
-        return(ua == null) ? "JOSM" : ua.toString();
+        pnlChangesetManagement.removeChangeset(cs);
     }
 
@@ -385,12 +331,5 @@
      */
     protected String getUploadComment() {
-        switch(southTabbedPane.getSelectedIndex()) {
-        case 0:
-            return pnlChangesetSelection.getUploadComment();
-        case 1:
-            TagModel tm = tagEditorPanel.getModel().get("comment");
-            return tm == null? "" : tm.getValue();
-        }
-        return "";
+        return pnlBasicUploadSettings.getUploadComment();
     }
 
@@ -405,5 +344,5 @@
 
     /**
-     * Sets whether the dialog was canceld
+     * Sets whether the dialog was canceled
      *
      * @param canceled true, if the dialog is canceled
@@ -423,65 +362,9 @@
                     )
             ).apply(this);
+            startUserInput();
         } else if (!visible && isShowing()){
             new WindowGeometry(this).remember(getClass().getName() + ".geometry");
         }
         super.setVisible(visible);
-    }
-
-    /**
-     * This change listener is triggered when current tab in the tabbed pane in
-     * the lower half of the dialog is changed.
-     *
-     * It's main purpose is to keep the content in the text field for the changeset
-     * comment in sync with the changeset tag "comment".
-     *
-     */
-    class TabbedPaneChangeLister implements ChangeListener {
-
-        protected boolean hasCommentTag() {
-            TagEditorModel model = tagEditorPanel.getModel();
-            return model.get("comment") != null;
-        }
-
-        protected TagModel getEmptyTag() {
-            TagEditorModel model = tagEditorPanel.getModel();
-            TagModel tm = model.get("");
-            if (tm != null) return tm;
-            tm = new TagModel("", "");
-            model.add(tm);
-            return tm;
-        }
-        protected TagModel getOrCreateCommentTag() {
-            TagEditorModel model = tagEditorPanel.getModel();
-            if (hasCommentTag())
-                return model.get("comment");
-            TagModel tm = getEmptyTag();
-            tm.setName("comment");
-            return tm;
-        }
-
-        protected void removeCommentTag() {
-            TagEditorModel model = tagEditorPanel.getModel();
-            model.delete("comment");
-        }
-
-        protected void refreshCommentTag() {
-            TagModel tm = getOrCreateCommentTag();
-            tm.setName("comment");
-            tm.setValue(pnlChangesetSelection.getUploadComment().trim());
-            if (pnlChangesetSelection.getUploadComment().trim().equals("")) {
-                removeCommentTag();
-            }
-            tagEditorPanel.getModel().fireTableDataChanged();
-        }
-
-        public void stateChanged(ChangeEvent e) {
-            if (southTabbedPane.getSelectedIndex() ==0) {
-                TagModel tm = tagEditorPanel.getModel().get("comment");
-                pnlChangesetSelection.initEditingOfUploadComment(tm == null ? "" : tm.getValue());
-            } else if (southTabbedPane.getSelectedIndex() == 1) {
-                refreshCommentTag();
-            }
-        }
     }
 
@@ -504,5 +387,4 @@
                     JOptionPane.ERROR_MESSAGE,
                     ht("/Dialog/UploadDialog#IllegalUploadComment")
-
             );
         }
@@ -518,10 +400,9 @@
         }
 
-
         public void actionPerformed(ActionEvent e) {
             if (getUploadComment().trim().length() < 3) {
                 warnIllegalUploadComment();
-                southTabbedPane.setSelectedIndex(0);
-                pnlChangesetSelection.initEditingOfUploadComment(getUploadComment());
+                tpConfigPanels.setSelectedIndex(0);
+                pnlBasicUploadSettings.initEditingOfUploadComment(getUploadComment());
                 return;
             }
@@ -530,6 +411,5 @@
                 if (strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
                     warnIllegalChunkSize();
-                    southTabbedPane.setSelectedIndex(0);
-                    pnlChangesetSelection.initEditingOfChunkSize();
+                    tpConfigPanels.setSelectedIndex(0);
                     return;
                 }
@@ -558,53 +438,4 @@
 
     /**
-     * A simple list of OSM primitives.
-     *
-     */
-    class PrimitiveList extends JList {
-        public PrimitiveList() {
-            super(new PrimitiveListModel());
-        }
-
-        public PrimitiveListModel getPrimitiveListModel() {
-            return (PrimitiveListModel)getModel();
-        }
-    }
-
-    /**
-     * A list model for a list of OSM primitives.
-     *
-     */
-    class PrimitiveListModel extends AbstractListModel{
-        private List<OsmPrimitive> primitives;
-
-        public PrimitiveListModel() {
-            primitives = new ArrayList<OsmPrimitive>();
-        }
-
-        public PrimitiveListModel(List<OsmPrimitive> primitives) {
-            setPrimitives(primitives);
-        }
-
-        public void setPrimitives(List<OsmPrimitive> primitives) {
-            if (primitives == null) {
-                this.primitives = new ArrayList<OsmPrimitive>();
-            } else {
-                this.primitives = primitives;
-            }
-            fireContentsChanged(this,0,getSize());
-        }
-
-        public Object getElementAt(int index) {
-            if (primitives == null) return null;
-            return primitives.get(index);
-        }
-
-        public int getSize() {
-            if (primitives == null) return 0;
-            return primitives.size();
-        }
-    }
-
-    /**
      * Listens to window closing events and processes them as cancel events.
      * Listens to window open events and initializes user input
@@ -619,584 +450,27 @@
         @Override
         public void windowOpened(WindowEvent e) {
-            startUserInput();
-        }
-    }
-
-    /**
-     * The panel which provides various UI widgets for controlling how to use
-     * changesets during upload.
-     *
-     */
-    class ChangesetSelectionPanel extends JPanel implements ListDataListener{
-
-        private ButtonGroup bgUseNewOrExisting;
-        private JRadioButton rbUseNew;
-        private JRadioButton rbExisting;
-        private JComboBox cbOpenChangesets;
-        private JButton btnRefresh;
-        private JButton btnClose;
-        private JCheckBox cbCloseAfterUpload;
-        private OpenChangesetModel model;
-        private HistoryComboBox cmt;
-        private UploadStrategySelectionPanel pnlUploadStrategy;
-
-        /**
-         * build the panel with the widgets for controlling whether an atomic upload
-         * should be used or not
-         *
-         * @return the panel
-         */
-        protected JPanel buildUploadStrategySelectionPanel() {
-            pnlUploadStrategy = new UploadStrategySelectionPanel();
-            pnlUploadStrategy.initFromPreferences();
-            return pnlUploadStrategy;
-        }
-
-        protected JPanel buildUploadCommentPanel() {
-            JPanel pnl = new JPanel();
-            pnl.setLayout(new GridBagLayout());
-            pnl.add(new JLabel(tr("Provide a brief comment for the changes you are uploading:")), GBC.eol().insets(0, 5, 10, 3));
-            cmt = new HistoryComboBox();
-            cmt.setToolTipText(tr("Enter an upload comment (min. 3 characters)"));
-            List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(HISTORY_KEY, new LinkedList<String>()));
-            // we have to reverse the history, because ComboBoxHistory will reverse it again
-            // in addElement()
-            //
-            Collections.reverse(cmtHistory);
-            cmt.setPossibleItems(cmtHistory);
-            cmt.getEditor().addActionListener(
-                    new ActionListener() {
-                        public void actionPerformed(ActionEvent e) {
-                            TagModel tm = tagEditorPanel.getModel().get("comment");
-                            if (tm == null) {
-                                tagEditorPanel.getModel().add(new TagModel("comment", cmt.getText()));
-                            } else {
-                                tm.setValue(cmt.getText());
-                            }
-                            tagEditorPanel.getModel().fireTableDataChanged();
-                        }
-                    }
-            );
-            cmt.getEditor().addActionListener(
-                    new ActionListener() {
-                        public void actionPerformed(ActionEvent e) {
-                            btnUpload.requestFocusInWindow();
-                        }
-                    }
-            );
-            pnl.add(cmt, GBC.eol().fill(GBC.HORIZONTAL));
-            return pnl;
-        }
-
-        protected void build() {
-            setLayout(new GridBagLayout());
-            GridBagConstraints gc = new GridBagConstraints();
-
-            bgUseNewOrExisting = new ButtonGroup();
-
-            // -- atomic upload
-            gc.gridwidth = 4;
-            gc.gridy = 0;
-            gc.fill = GridBagConstraints.HORIZONTAL;
-            gc.weightx = 1.0;
-            gc.anchor = GridBagConstraints.FIRST_LINE_START;
-            add(buildUploadStrategySelectionPanel(), gc);
-
-            // -- changeset command
-            gc.gridwidth = 4;
-            gc.gridy = 1;
-            gc.fill = GridBagConstraints.HORIZONTAL;
-            gc.weightx = 1.0;
-            gc.anchor = GridBagConstraints.FIRST_LINE_START;
-            add(buildUploadCommentPanel(), gc);
-
-            gc.gridwidth = 4;
-            gc.gridy = 2;
-            gc.fill = GridBagConstraints.HORIZONTAL;
-            gc.weightx = 0.0;
-            gc.anchor = GridBagConstraints.FIRST_LINE_START;
-            rbUseNew = new JRadioButton(tr("Open a new changeset"));
-            rbUseNew.setToolTipText(tr("Open a new changeset and use it in the next upload"));
-            bgUseNewOrExisting.add(rbUseNew);
-            add(rbUseNew, gc);
-
-            gc.gridx = 0;
-            gc.gridy = 3;
-            gc.gridwidth = 1;
-            rbExisting = new JRadioButton(tr("Use an open changeset"));
-            rbExisting.setToolTipText(tr("Upload data to an already opened changeset"));
-            bgUseNewOrExisting.add(rbExisting);
-            add(rbExisting, gc);
-
-            gc.gridx = 1;
-            gc.gridy = 3;
-            gc.gridwidth = 1;
-            gc.weightx = 1.0;
-            model = new OpenChangesetModel();
-            cbOpenChangesets = new JComboBox(model);
-            cbOpenChangesets.setToolTipText("Select an open changeset");
-            cbOpenChangesets.setRenderer(new ChangesetCellRenderer());
-            cbOpenChangesets.addItemListener(new ChangesetListItemStateListener());
-            Dimension d = cbOpenChangesets.getPreferredSize();
-            d.width = 200;
-            cbOpenChangesets.setPreferredSize(d);
-            d.width = 100;
-            cbOpenChangesets.setMinimumSize(d);
-            model.addListDataListener(this);
-            add(cbOpenChangesets, gc);
-
-            gc.gridx = 2;
-            gc.gridy = 3;
-            gc.gridwidth = 1;
-            gc.weightx = 0.0;
-            btnRefresh = new JButton(new RefreshAction());
-            add(btnRefresh, gc);
-
-            gc.gridx = 3;
-            gc.gridy = 3;
-            gc.gridwidth = 1;
-            gc.weightx = 0.0;
-            CloseChangesetAction closeChangesetAction = new CloseChangesetAction();
-            btnClose = new JButton(closeChangesetAction);
-            cbOpenChangesets.addItemListener(closeChangesetAction);
-            add(btnClose, gc);
-
-            gc.gridx = 0;
-            gc.gridy = 4;
-            gc.gridwidth = 4;
-            cbCloseAfterUpload = new JCheckBox(tr("Close changeset after upload"));
-            cbCloseAfterUpload.setToolTipText(tr("Select to close the changeset after the next upload"));
-            add(cbCloseAfterUpload, gc);
-            cbCloseAfterUpload.setSelected(true);
-
-            rbUseNew.getModel().addItemListener(new RadioButtonHandler());
-            rbExisting.getModel().addItemListener(new RadioButtonHandler());
-
-            refreshGUI();
-        }
-
-        public ChangesetSelectionPanel() {
-            build();
-        }
-
-        /**
-         * Remembers the user input in the preference settings
-         */
-        public void rememberUserInput() {
-            // store the history of comments
-            cmt.addCurrentItemToHistory();
-            Main.pref.putCollection(HISTORY_KEY, cmt.getHistory());
-            pnlUploadStrategy.saveToPreferences();
-        }
-
-        /**
-         * Initializes the panel for user input
-         */
-        public void startUserInput() {
-            List<String> history = cmt.getHistory();
-            if (history != null && !history.isEmpty()) {
-                cmt.setText(history.get(0));
+            //startUserInput();
+        }
+
+        @Override
+        public void windowActivated(WindowEvent arg0) {
+            if (tpConfigPanels.getSelectedIndex() == 0) {
+                pnlBasicUploadSettings.initEditingOfUploadComment(getUploadComment());
             }
-            cmt.requestFocusInWindow();
-            cmt.getEditor().getEditorComponent().requestFocusInWindow();
-        }
-
-        public void prepareDialogForNextUpload(Changeset cs) {
-            if (cs == null || cs.getId() == 0) {
-                rbUseNew.setSelected(true);
-                cbCloseAfterUpload.setSelected(true);
-            } if (cs.getId() == 0) {
-                rbUseNew.setSelected(true);
-                cbCloseAfterUpload.setSelected(true);
-            } else if (cs.isOpen()) {
-                rbExisting.setSelected(true);
-                cbCloseAfterUpload.setSelected(false);
+        }
+    }
+
+    /* -------------------------------------------------------------------------- */
+    /* Interface PropertyChangeListener                                           */
+    /* -------------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
+            Changeset cs = (Changeset)evt.getNewValue();
+            if (cs == null) {
+                tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
             } else {
-                rbUseNew.setSelected(true);
-                cbCloseAfterUpload.setSelected(true);
+                tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
             }
         }
-
-        /**
-         * Replies the current upload comment
-         *
-         * @return
-         */
-        public String getUploadComment() {
-            return cmt.getText();
-        }
-
-        /**
-         * Replies the current upload comment
-         *
-         * @return
-         */
-        public void setUploadComment(String uploadComment) {
-            cmt.setText(uploadComment);
-        }
-
-        public void initEditingOfUploadComment(String comment) {
-            setUploadComment(comment);
-            cmt.getEditor().selectAll();
-            cmt.requestFocusInWindow();
-        }
-
-        public void initEditingOfChunkSize() {
-            pnlUploadStrategy.initEditingOfChunkSize();
-        }
-
-        protected void refreshGUI() {
-            rbExisting.setEnabled(model.getSize() > 0);
-            if (model.getSize() == 0) {
-                if (!rbUseNew.isSelected()) {
-                    rbUseNew.setSelected(true);
-                }
-            }
-            cbOpenChangesets.setEnabled(model.getSize() > 0 && rbExisting.isSelected());
-        }
-
-        public void contentsChanged(ListDataEvent e) {
-            refreshGUI();
-        }
-
-        public void intervalAdded(ListDataEvent e) {
-            refreshGUI();
-        }
-
-        public void intervalRemoved(ListDataEvent e) {
-            refreshGUI();
-        }
-
-        public Changeset getChangeset() {
-            if (rbUseNew.isSelected() || cbOpenChangesets.getSelectedItem() == null)
-                return new Changeset();
-            Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
-            if (cs == null)
-                return new Changeset();
-            return cs;
-        }
-
-        /**
-         * Replies the {@see UploadStrategySpecification} the user entered in the dialog.
-         * 
-         * @return the {@see UploadStrategySpecification} the user entered in the dialog.
-         */
-        public UploadStrategySpecification getUploadStrategySpecification() {
-            return pnlUploadStrategy.getUploadStrategySpecification();
-        }
-
-        public void setOrUpdateChangeset(Changeset cs) {
-            if (cs == null) {
-                cs = new Changeset();
-                cs.put("created_by", getDefaultCreatedBy());
-                tagEditorPanel.getModel().initFromPrimitive(cs);
-                tagEditorPanel.getModel().appendNewTag();
-                prepareDialogForNextUpload(cs);
-            } else if (cs.getId() == 0) {
-                if (cs.get("created_by") == null) {
-                    cs.put("created_by", getDefaultCreatedBy());
-                }
-                tagEditorPanel.getModel().initFromPrimitive(cs);
-                tagEditorPanel.getModel().appendNewTag();
-                prepareDialogForNextUpload(cs);
-            } else if (cs.getId() > 0 && cs.isOpen()){
-                if (cs.get("created_by") == null) {
-                    cs.put("created_by", getDefaultCreatedBy());
-                }
-                tagEditorPanel.getModel().initFromPrimitive(cs);
-                model.addOrUpdate(cs);
-                cs = model.getChangesetById(cs.getId());
-                cbOpenChangesets.setSelectedItem(cs);
-                prepareDialogForNextUpload(cs);
-            } else if (cs.getId() > 0 && !cs.isOpen()){
-                removeChangeset(cs);
-            }
-        }
-
-        /**
-         * Remove a changeset from the list of open changeset
-         *
-         * @param cs the changeset to be removed. Ignored if null.
-         */
-        public void removeChangeset(Changeset cs) {
-            if (cs ==  null) return;
-            Changeset selected = (Changeset)model.getSelectedItem();
-            model.removeChangeset(cs);
-            if (model.getSize() == 0 || selected == cs) {
-                // no more changesets or removed changeset is the currently selected
-                // changeset? Switch to using a new changeset.
-                //
-                rbUseNew.setSelected(true);
-                model.setSelectedItem(null);
-                southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
-
-                cs = new Changeset();
-                if (cs.get("created_by") == null) {
-                    cs.put("created_by", getDefaultCreatedBy());
-                    cs.put("comment", getUploadComment());
-                }
-                tagEditorPanel.getModel().initFromPrimitive(cs);
-            }
-            prepareDialogForNextUpload(cs);
-        }
-
-        /**
-         * Sets whether a new changeset is to be used
-         *
-         */
-        public void setUseNewChangeset() {
-            rbUseNew.setSelected(true);
-        }
-
-        /**
-         * Sets whether an existing changeset is to be used
-         */
-        public void setUseExistingChangeset() {
-            rbExisting.setSelected(true);
-            if (cbOpenChangesets.getSelectedItem() == null && model.getSize() > 0) {
-                cbOpenChangesets.setSelectedItem(model.getElementAt(0));
-            }
-        }
-
-        /**
-         * Replies true if the selected changeset should be closed after the
-         * next upload
-         *
-         * @return true if the selected changeset should be closed after the
-         * next upload
-         */
-        public boolean isCloseAfterUpload() {
-            return cbCloseAfterUpload.isSelected();
-        }
-
-        public void setNumUploadedObjects(int numUploadedObjects) {
-            pnlUploadStrategy.setNumUploadedObjects(numUploadedObjects);
-        }
-
-        class RadioButtonHandler implements ItemListener {
-            public void itemStateChanged(ItemEvent e) {
-                if (rbUseNew.isSelected()) {
-                    southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
-                    // init a new changeset from the currently edited tags
-                    // and the comment field
-                    //
-                    Changeset cs = new Changeset();
-                    tagEditorPanel.getModel().applyToPrimitive(cs);
-                    if (cs.get("created_by") == null) {
-                        cs.put("created_by", getDefaultCreatedBy());
-                    }
-                    cs.put("comment", cmt.getText());
-                    tagEditorPanel.getModel().initFromPrimitive(cs);
-                } else {
-                    if (cbOpenChangesets.getSelectedItem() == null) {
-                        model.selectFirstChangeset();
-                    }
-                    Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
-                    if (cs != null) {
-                        cs.put("comment", cmt.getText());
-                        southTabbedPane.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
-                        tagEditorPanel.getModel().initFromPrimitive(cs);
-                    }
-                }
-                refreshGUI();
-            }
-        }
-
-        class ChangesetListItemStateListener implements ItemListener {
-            public void itemStateChanged(ItemEvent e) {
-                Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
-                if (cs == null) {
-                    southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
-                    // init a new changeset from the currently edited tags
-                    // and the comment field
-                    //
-                    cs = new Changeset();
-                    tagEditorPanel.getModel().applyToPrimitive(cs);
-                    if (cs.get("created_by") == null) {
-                        cs.put("created_by", getDefaultCreatedBy());
-                    }
-                    cs.put("comment", cmt.getText());
-                    tagEditorPanel.getModel().initFromPrimitive(cs);
-                } else {
-                    southTabbedPane.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
-                    if (cs.get("created_by") == null) {
-                        cs.put("created_by", getDefaultCreatedBy());
-                    }
-                    tagEditorPanel.getModel().initFromPrimitive(cs);
-                    if (cs.get("comment") != null) {
-                        cmt.setText(cs.get("comment"));
-                    }
-                }
-            }
-        }
-
-        /**
-         * Refreshes the list of open changesets
-         *
-         */
-        class RefreshAction extends AbstractAction {
-            public RefreshAction() {
-                //putValue(NAME, tr("Reload"));
-                putValue(SHORT_DESCRIPTION, tr("Load the list of your open changesets from the server"));
-                putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
-            }
-
-            public void actionPerformed(ActionEvent e) {
-                DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(model);
-                Main.worker.submit(task);
-            }
-        }
-
-        class CloseChangesetAction extends AbstractAction implements ItemListener{
-            public CloseChangesetAction() {
-                putValue(NAME, tr("Close"));
-                putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
-                putValue(SHORT_DESCRIPTION, tr("Close the currently selected open changeset"));
-                refreshEnabledState();
-            }
-
-            public void actionPerformed(ActionEvent e) {
-                Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
-                if (cs == null) return;
-                CloseChangesetTask task = new CloseChangesetTask(Collections.singletonList(cs));
-                Main.worker.submit(task);
-            }
-
-            protected void refreshEnabledState() {
-                setEnabled(cbOpenChangesets.getModel().getSize() > 0 && cbOpenChangesets.getSelectedItem() != null);
-            }
-
-            public void itemStateChanged(ItemEvent e) {
-                refreshEnabledState();
-            }
-        }
-    }
-
-    /**
-     * A combobox model for the list of open changesets
-     *
-     */
-    public class OpenChangesetModel extends DefaultComboBoxModel {
-        private List<Changeset> changesets;
-        private long uid;
-        private Changeset selectedChangeset = null;
-
-        protected Changeset getChangesetById(long id) {
-            for (Changeset cs : changesets) {
-                if (cs.getId() == id) return cs;
-            }
-            return null;
-        }
-
-        public OpenChangesetModel() {
-            this.changesets = new ArrayList<Changeset>();
-        }
-
-        protected void internalAddOrUpdate(Changeset cs) {
-            Changeset other = getChangesetById(cs.getId());
-            if (other != null) {
-                cs.cloneFrom(other);
-            } else {
-                changesets.add(cs);
-            }
-        }
-
-        public void addOrUpdate(Changeset cs) {
-            if (cs.getId() <= 0 )
-                throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", cs.getId()));
-            internalAddOrUpdate(cs);
-            fireContentsChanged(this, 0, getSize());
-        }
-
-        public void remove(long id) {
-            Changeset cs = getChangesetById(id);
-            if (cs != null) {
-                changesets.remove(cs);
-            }
-            fireContentsChanged(this, 0, getSize());
-        }
-
-        public void setChangesets(Collection<Changeset> changesets) {
-            this.changesets.clear();
-            if (changesets != null) {
-                for (Changeset cs: changesets) {
-                    internalAddOrUpdate(cs);
-                }
-            }
-            fireContentsChanged(this, 0, getSize());
-            if (getSelectedItem() == null && !this.changesets.isEmpty()) {
-                setSelectedItem(this.changesets.get(0));
-            } else if (getSelectedItem() != null) {
-                if (changesets.contains(getSelectedItem())) {
-                    setSelectedItem(getSelectedItem());
-                } else if (!this.changesets.isEmpty()){
-                    setSelectedItem(this.changesets.get(0));
-                } else {
-                    setSelectedItem(null);
-                }
-            } else {
-                setSelectedItem(null);
-            }
-        }
-
-        public void setUserId(long uid) {
-            this.uid = uid;
-        }
-
-        public long getUserId() {
-            return uid;
-        }
-
-        public void selectFirstChangeset() {
-            if (changesets == null || changesets.isEmpty()) return;
-            setSelectedItem(changesets.get(0));
-        }
-
-        public void removeChangeset(Changeset cs) {
-            if (cs == null) return;
-            changesets.remove(cs);
-            if (selectedChangeset == cs) {
-                selectFirstChangeset();
-            }
-            fireContentsChanged(this, 0, getSize());
-        }
-        /* ------------------------------------------------------------------------------------ */
-        /* ComboBoxModel                                                                        */
-        /* ------------------------------------------------------------------------------------ */
-        @Override
-        public Object getElementAt(int index) {
-            return changesets.get(index);
-        }
-
-        @Override
-        public int getIndexOf(Object anObject) {
-            return changesets.indexOf(anObject);
-        }
-
-        @Override
-        public int getSize() {
-            return changesets.size();
-        }
-
-        @Override
-        public Object getSelectedItem() {
-            return selectedChangeset;
-        }
-
-        @Override
-        public void setSelectedItem(Object anObject) {
-            if (anObject == null) {
-                this.selectedChangeset = null;
-                super.setSelectedItem(null);
-                return;
-            }
-            if (! (anObject instanceof Changeset)) return;
-            Changeset cs = (Changeset)anObject;
-            if (cs.getId() == 0 || ! cs.isOpen()) return;
-            Changeset candidate = getChangesetById(cs.getId());
-            if (candidate == null) return;
-            this.selectedChangeset = candidate;
-            super.setSelectedItem(selectedChangeset);
-        }
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java	(revision 2599)
@@ -41,5 +41,4 @@
     private ProgressMonitor monitor;
     private Changeset changeset;
-    private boolean closeChangesetAfterUpload;
     private Collection<OsmPrimitive> toUpload;
     private HashSet<OsmPrimitive> processedPrimitives;
@@ -53,9 +52,8 @@
      * @param monitor  a progress monitor. If monitor is null, uses {@see NullProgressMonitor#INSTANCE}
      * @param changeset the changeset to be used
-     * @param closeChangesetAfterUpload true, if the changeset should be closed after the upload
      * @throws IllegalArgumentException thrown, if layer is null
      * @throws IllegalArgumentException thrown if strategy is null
      */
-    public UploadLayerTask(UploadStrategySpecification strategy, OsmDataLayer layer, ProgressMonitor monitor, Changeset changeset, boolean closeChangesetAfterUpload) {
+    public UploadLayerTask(UploadStrategySpecification strategy, OsmDataLayer layer, ProgressMonitor monitor, Changeset changeset) {
         if (layer == null)
             throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
@@ -69,5 +67,4 @@
         this.changeset = changeset;
         this.strategy = strategy;
-        this.closeChangesetAfterUpload = closeChangesetAfterUpload;
         processedPrimitives = new HashSet<OsmPrimitive>();
     }
@@ -134,5 +131,5 @@
                 }
             }
-            if (closeChangesetAfterUpload) {
+            if (strategy.isCloseChangesetAfterUpload()) {
                 if (changeset != null && changeset.getId() > 0) {
                     OsmApi.getOsmApi().closeChangeset(changeset, monitor.createSubTaskMonitor(0, false));
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadParameterSummaryPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadParameterSummaryPanel.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadParameterSummaryPanel.java	(revision 2599)
@@ -0,0 +1,214 @@
+// 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.Font;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.MessageFormat;
+import java.util.logging.Logger;
+
+import javax.swing.BorderFactory;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class UploadParameterSummaryPanel extends JPanel implements HyperlinkListener, PropertyChangeListener{
+
+    static private final Logger logger = Logger.getLogger(UploadParameterSummaryPanel.class.getName());
+
+    private UploadStrategySpecification spec = new UploadStrategySpecification();
+    private int numObjects;
+    private JEditorPane jepMessage;
+    private JLabel lblWarning;
+
+    private Changeset selectedChangeset;
+    private boolean closeChangesetAfterNextUpload;
+    private ConfigurationParameterRequestHandler configHandler;
+
+    protected String buildChangsetSummary() {
+        StringBuffer msg = new StringBuffer();
+        if (selectedChangeset == null || selectedChangeset.isNew()) {
+            msg.append(tr("Objects are uploaded to a <strong>new changeset</strong>."));
+        } else {
+            String uploadComment = selectedChangeset.get("comment") == null ?
+                    "" : selectedChangeset.get("comment");
+            msg.append(tr("Objects are uploaded to the <strong>open changeset</strong> {0} with upload comment ''{1}''.",
+                    selectedChangeset.getId(),
+                    uploadComment
+            ));
+        }
+        if (closeChangesetAfterNextUpload) {
+            msg.append(tr("The changeset is going to be <strong>closed</strong> after this upload"));
+        } else {
+            msg.append(tr("The changeset is <strong>left open</strong> after this upload"));
+        }
+        msg.append(tr(" (<a href=\"urn:changeset-configuration\">configure changeset</a>)"));
+        return msg.toString();
+    }
+
+    protected String buildStrategySummary() {
+        if (spec == null)
+            return "";
+        // check whether we can use one changeset only or whether we have to use
+        // multiple changesets
+        //
+        boolean useOneChangeset = true;
+        int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangsetSize();
+        if (maxChunkSize > 0 && numObjects > maxChunkSize) {
+            useOneChangeset = false;
+        }
+
+        int numRequests = spec.getNumRequests(numObjects);
+        String msg = null;
+        if (useOneChangeset) {
+            lblWarning.setVisible(false);
+            if (numRequests == 0) {
+                msg = trn(
+                        "Uploading <strong>{0} object</strong> to <strong>1 changeset</strong>",
+                        "Uploading <strong>{0} objects</strong> to <strong>1 changeset</strong>",
+                        numObjects, numObjects
+                );
+            } else if (numRequests == 1) {
+                msg = trn(
+                        "Uploading <strong>{0} object</strong> to <strong>1 changeset</strong> using <strong>1 request</strong>",
+                        "Uploading <strong>{0} objects</strong> to <strong>1 changeset</strong> using <strong>1 request</strong>",
+                        numObjects, numObjects
+                );
+            } else if (numRequests > 1){
+                msg = tr("Uploading <strong>{0} objects</strong> to <strong>1 changeset</strong> using <strong>{1} requests</strong>", numObjects, numRequests);
+            }
+            msg = msg + " (<a href=\"urn:advanced-configuration\">advanced configuration</a>)";
+        } else {
+            lblWarning.setVisible(true);
+            if (numRequests == 0) {
+                msg = tr("{0} objects exceed the max. allowed {1} objects in a changeset on the server ''{2}''. Please <a href=\"urn:advanced-configuration\">configure</a> how to proceed with <strong>multiple changesets</strong>",
+                        numObjects, maxChunkSize, OsmApi.getOsmApi().getBaseUrl());
+            } else if (numRequests > 1){
+                msg = tr("Uploading <strong>{0} objects</strong> to <strong>multiple changesets</strong> using <strong>{1} requests</strong>", numObjects, numRequests);
+                msg = msg + " (<a href=\"urn:advanced-configuration\">advanced configuration</a>)";
+            }
+        }
+        return msg;
+    }
+
+    protected void build() {
+        jepMessage = new JEditorPane("text/html", "");
+        jepMessage.setOpaque(false);
+        jepMessage.setEditable(false);
+        jepMessage.addHyperlinkListener(this);
+        Font f = UIManager.getFont("Label.font");
+        StyleSheet ss = new StyleSheet();
+        String rule = MessageFormat.format(
+                "font-family: ''{0}'';font-size: {1,number}pt; font-weight: {2}; font-style: {3}",
+                f.getName(),
+                f.getSize(),
+                f.isBold() ? "bold" : "normal",
+                        f.isItalic() ? "italic" : "normal"
+        );
+        rule = "body {" + rule + "}";
+        rule = MessageFormat.format(
+                "font-family: ''{0}'';font-size: {1,number}pt; font-weight: {2}; font-style: {3}",
+                f.getName(),
+                f.getSize(),
+                "bold",
+                f.isItalic() ? "italic" : "normal"
+        );
+        rule = "strong {" + rule + "}";
+        ss.addRule(rule);
+        ss.addRule("a {text-decoration: underline; color: blue}");
+        HTMLEditorKit kit = new HTMLEditorKit();
+        kit.setStyleSheet(ss);
+        jepMessage.setEditorKit(kit);
+
+        setLayout(new BorderLayout());
+        add(jepMessage, BorderLayout.CENTER);
+        lblWarning = new JLabel("");
+        lblWarning.setVisible(false);
+        lblWarning.setIcon(ImageProvider.get("warning-small.png"));
+        lblWarning.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+        JPanel pnl = new JPanel(new BorderLayout());
+        pnl.add(lblWarning, BorderLayout.NORTH);
+        add(pnl, BorderLayout.WEST);
+    }
+
+    public UploadParameterSummaryPanel() {
+        build();
+        updateSummary();
+    }
+
+    public void setConfigurationParameterRequestListener(ConfigurationParameterRequestHandler handler) {
+        this.configHandler = handler;
+    }
+
+    public void setUploadStrategySpecification(UploadStrategySpecification spec) {
+        this.spec = spec;
+        updateSummary();
+    }
+
+    public void setNumObjects(int numObjects) {
+        this.numObjects = numObjects;
+        updateSummary();
+    }
+
+    public void setCloseChangesetAfterNextUpload(boolean value) {
+        this.closeChangesetAfterNextUpload = value;
+        updateSummary();
+    }
+
+    protected void updateSummary() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("<html>");
+        sb.append(buildStrategySummary());
+        sb.append("<br><br>");
+        sb.append(buildChangsetSummary());
+        sb.append("</html>");
+        jepMessage.setText(sb.toString());
+    }
+
+    /* --------------------------------------------------------------------- */
+    /* Interface HyperlinkListener
+    /* --------------------------------------------------------------------- */
+    public void hyperlinkUpdate(HyperlinkEvent e) {
+        if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
+            if (e.getDescription() == null || configHandler == null)
+                return;
+            if (e.getDescription().equals("urn:changeset-configuration")) {
+                configHandler.handleChangesetConfigurationRequest();
+            } else if (e.getDescription().equals("urn:advanced-configuration")) {
+                configHandler.handleUploadStrategyConfigurationRequest();
+            }
+        }
+    }
+
+    /* --------------------------------------------------------------------- */
+    /* Interface PropertyChangeListener
+    /* --------------------------------------------------------------------- */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
+            selectedChangeset = (Changeset)evt.getNewValue();
+            updateSummary();
+        } else if (evt.getPropertyName().equals(ChangesetManagementPanel.CLOSE_CHANGESET_AFTER_UPLOAD)) {
+            closeChangesetAfterNextUpload = (Boolean)evt.getNewValue();
+            updateSummary();
+        } else if (evt.getPropertyName().equals(UploadedObjectsSummaryPanel.NUM_OBJECTS_TO_UPLOAD_PROP)) {
+            numObjects = (Integer)evt.getNewValue();
+            updateSummary();
+        } else if (evt.getPropertyName().equals(UploadStrategySelectionPanel.UPLOAD_STRATEGY_SPECIFICATION_PROP)) {
+            this.spec = (UploadStrategySpecification)evt.getNewValue();
+            updateSummary();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java	(revision 2599)
@@ -0,0 +1,350 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.io;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashSet;
+import java.util.logging.Logger;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.ChangesetClosedException;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
+import org.openstreetmap.josm.io.OsmServerWriter;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.xml.sax.SAXException;
+
+/**
+ * The task for uploading a collection of primitives
+ *
+ */
+public class UploadPrimitivesTask extends  AbstractUploadTask {
+    static private final Logger logger = Logger.getLogger(UploadPrimitivesTask.class.getName());
+
+    private boolean uploadCancelled = false;
+    private Exception lastException = null;
+    private APIDataSet toUpload;
+    private OsmServerWriter writer;
+    private OsmDataLayer layer;
+    private Changeset changeset;
+    private HashSet<OsmPrimitive> processedPrimitives;
+    private UploadStrategySpecification strategy;
+
+    /**
+     * Creates the task
+     * 
+     * @param strategy the upload strategy. Must not be null.
+     * @param layer  the OSM data layer for which data is uploaded. Must not be null.
+     * @param toUpload the collection of primitives to upload. Set to the empty collection if null.
+     * @param changeset the changeset to use for uploading. Must not be null. changeset.getId()
+     * can be 0 in which case the upload task creates a new changeset
+     * @throws IllegalArgumentException thrown if layer is null
+     * @throws IllegalArgumentException thrown if toUpload is null
+     * @throws IllegalArgumentException thrown if strategy is null
+     * @throws IllegalArgumentException thrown if changeset is null
+     */
+    public UploadPrimitivesTask(UploadStrategySpecification strategy, OsmDataLayer layer, APIDataSet toUpload, Changeset changeset) {
+        super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);
+        ensureParameterNotNull(layer,"layer");
+        ensureParameterNotNull(strategy, "strategy");
+        ensureParameterNotNull(changeset, "changeset");
+        this.toUpload = toUpload;
+        this.layer = layer;
+        this.changeset = changeset;
+        this.strategy = strategy;
+        this.processedPrimitives = new HashSet<OsmPrimitive>();
+    }
+
+    protected MaxChangesetSizeExceededPolicy askMaxChangesetSizeExceedsPolicy() {
+        ButtonSpec[] specs = new ButtonSpec[] {
+                new ButtonSpec(
+                        tr("Continue uploading"),
+                        ImageProvider.get("upload"),
+                        tr("Click to continue uploading to additional new changesets"),
+                        null /* no specific help text */
+                ),
+                new ButtonSpec(
+                        tr("Go back to Upload Dialog"),
+                        ImageProvider.get("dialogs", "uploadproperties"),
+                        tr("Click to return to the Upload Dialog"),
+                        null /* no specific help text */
+                ),
+                new ButtonSpec(
+                        tr("Abort"),
+                        ImageProvider.get("cancel"),
+                        tr("Click to abort uploading"),
+                        null /* no specific help text */
+                )
+        };
+        int numObjectsToUploadLeft = toUpload.getSize() - processedPrimitives.size();
+        String msg1 = tr("The server reported that the current changset was closed.<br>"
+                + "This is most likely because the changesets size exceeded the max. size<br>"
+                + "of {0} objects on the server ''{1}''.",
+                OsmApi.getOsmApi().getCapabilities().getMaxChangsetSize(),
+                OsmApi.getOsmApi().getBaseUrl()
+        );
+        String msg2 = trn(
+                "There is {0} object to upload left.",
+                "There are {0} objects to upload left.",
+                numObjectsToUploadLeft,
+                numObjectsToUploadLeft
+        );
+        String msg3 = tr(
+                "Click ''<strong>{0}</strong>'' to continue uploading to additional new changsets.<br>"
+                + "Click ''<strong>{1}</strong>'' to return to the upload dialog.<br>"
+                + "Click ''<strong>{2}</strong>'' to abort uploading and return to map editing.<br>",
+                specs[0].text,
+                specs[1].text,
+                specs[2].text
+        );
+        String msg = "<html>" + msg1 + "<br><br>" + msg2 +"<br><br>" + msg3 + "</html>";
+        int ret = HelpAwareOptionPane.showOptionDialog(
+                Main.parent,
+                msg,
+                tr("Changeset is full"),
+                JOptionPane.WARNING_MESSAGE,
+                null, /* no special icon */
+                specs,
+                specs[0],
+                ht("/Action/UploadAction#ChangsetFull")
+        );
+        switch(ret) {
+        case 0: return MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS;
+        case 1: return MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG;
+        case 2: return MaxChangesetSizeExceededPolicy.ABORT;
+        case JOptionPane.CLOSED_OPTION: return MaxChangesetSizeExceededPolicy.ABORT;
+        }
+        // should not happen
+        return null;
+    }
+
+    protected void openNewChangset() {
+        // make sure the current changeset is removed from the upload dialog.
+        //
+        UploadDialog.getUploadDialog().removeChangeset(changeset);
+        Changeset newChangeSet = new Changeset();
+        newChangeSet.setKeys(this.changeset.getKeys());
+        this.changeset = new Changeset();
+    }
+
+    protected boolean recoverFromChangsetFullException() {
+        if (toUpload.getSize() - processedPrimitives.size() == 0) {
+            strategy.setPolicy(MaxChangesetSizeExceededPolicy.ABORT);
+            return false;
+        }
+        if (strategy.getPolicy() == null || strategy.getPolicy().equals(MaxChangesetSizeExceededPolicy.ABORT)) {
+            MaxChangesetSizeExceededPolicy policy = askMaxChangesetSizeExceedsPolicy();
+            strategy.setPolicy(policy);
+        }
+        switch(strategy.getPolicy()) {
+        case ABORT:
+            // don't continue - finish() will send the user back to map editing
+            //
+            return false;
+        case FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG:
+            // don't continue - finish() will send the user back to the upload dialog
+            //
+            return false;
+        case AUTOMATICALLY_OPEN_NEW_CHANGESETS:
+            // prepare the state of the task for a next iteration in uploading.
+            //
+            openNewChangset();
+            toUpload.removeProcessed(processedPrimitives);
+            return true;
+        }
+        // should not happen
+        return false;
+    }
+
+    /**
+     * Retries to recover the upload operation from an exception which was thrown because
+     * an uploaded primitive was already deleted on the server.
+     *
+     * @param e the exception throw by the API
+     * @param monitor a progress monitor
+     * @throws OsmTransferException  thrown if we can't recover from the exception
+     */
+    protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException{
+        if (!e.isKnownPrimitive()) throw e;
+        OsmPrimitive p = layer.data.getPrimitiveById(e.getPrimitiveId(), e.getPrimitiveType());
+        if (p == null) throw e;
+        if (p.isDeleted()) {
+            // we tried to delete an already deleted primitive.
+            //
+            System.out.println(tr("Warning: object ''{0}'' is already deleted on the server. Skipping this object and retrying to upload.", p.getDisplayName(DefaultNameFormatter.getInstance())));
+            monitor.appendLogMessage(tr("Object ''{0}'' is already deleted. Skipping object in upload.",p.getDisplayName(DefaultNameFormatter.getInstance())));
+            processedPrimitives.addAll(writer.getProcessedPrimitives());
+            processedPrimitives.add(p);
+            toUpload.removeProcessed(processedPrimitives);
+            return;
+        }
+        // exception was thrown because we tried to *update* an already deleted
+        // primitive. We can't resolve this automatically. Re-throw exception,
+        // a conflict is going to be created later.
+        throw e;
+    }
+
+    protected void cleanupAfterUpload() {
+        // we always clean up the data, even in case of errors. It's possible the data was
+        // partially uploaded. Better run on EDT.
+        //
+        Runnable r  = new Runnable() {
+            public void run() {
+                layer.cleanupAfterUpload(processedPrimitives);
+                layer.fireDataChange();
+                layer.onPostUploadToServer();
+
+                // make sure the upload dialog lists all known open changesets
+                //
+                if (lastException != null && lastException instanceof ChangesetClosedException) {
+                    UploadDialog.getUploadDialog().removeChangeset(changeset);
+                } else {
+                    UploadDialog.getUploadDialog().updateListOfChangesetsAfterUploadOperation(changeset);
+                }
+            }
+        };
+
+        try {
+            SwingUtilities.invokeAndWait(r);
+        } catch(InterruptedException e) {
+            lastException = e;
+        } catch(InvocationTargetException e) {
+            lastException = new OsmTransferException(e.getCause());
+        }
+    }
+
+    @Override protected void realRun() throws SAXException, IOException {
+        try {
+            uploadloop: while(true) {
+                try {
+                    getProgressMonitor().subTask(tr("Uploading {0} objects ...", toUpload.getSize()));
+                    synchronized(this) {
+                        writer = new OsmServerWriter();
+                    }
+                    writer.uploadOsm(strategy, toUpload.getPrimitives(), changeset, getProgressMonitor().createSubTaskMonitor(1, false));
+                    processedPrimitives.addAll(writer.getProcessedPrimitives());
+
+                    // if we get here we've successfully uploaded the data. Exit the loop.
+                    //
+                    break;
+                } catch(OsmApiPrimitiveGoneException e) {
+                    // try to recover from  410 Gone
+                    //
+                    recoverFromGoneOnServer(e, getProgressMonitor());
+                } catch(ChangesetClosedException e) {
+                    processedPrimitives.addAll(writer.getProcessedPrimitives());
+                    changeset.setOpen(false);
+                    switch(e.getSource()) {
+                    case UNSPECIFIED:
+                        throw e;
+                    case UPDATE_CHANGESET:
+                        // The changeset was closed when we tried to update it. Probably, our
+                        // local list of open changesets got out of sync with the server state.
+                        // The user will have to select another open changeset.
+                        // Rethrow exception - this will be handled later.
+                        //
+                        throw e;
+                    case UPLOAD_DATA:
+                        // Most likely the changeset is full. Try to recover and continue
+                        // with a new changeset, but let the user decide first (see
+                        // recoverFromChangsetFullException)
+                        //
+                        if (recoverFromChangsetFullException()) {
+                            continue;
+                        }
+                        lastException = e;
+                        break uploadloop;
+                    }
+                } finally {
+                    synchronized(this) {
+                        writer = null;
+                    }
+                }
+            }
+        // if required close the changeset
+        //
+        if (strategy.isCloseChangesetAfterUpload() && changeset != null && !changeset.isNew() && changeset.isOpen()) {
+            OsmApi.getOsmApi().closeChangeset(changeset, progressMonitor.createSubTaskMonitor(0, false));
+        }
+        } catch (Exception e) {
+            if (uploadCancelled) {
+                System.out.println(tr("Ignoring caught exception because upload is canceled. Exception is: {0}", e.toString()));
+                return;
+            }
+            lastException = e;
+        }
+        if (uploadCancelled) return;
+        cleanupAfterUpload();
+    }
+
+    @Override protected void finish() {
+        if (uploadCancelled)
+            return;
+        if (lastException == null)
+            return;
+
+        // depending on the success of the upload operation and on the policy for
+        // multi changeset uploads this will sent the user back to the appropriate
+        // place in JOSM, either
+        // - to an error dialog
+        // - to the Upload Dialog
+        // - to map editing
+        Runnable r = new Runnable() {
+            public void run() {
+                if (lastException instanceof ChangesetClosedException) {
+                    ChangesetClosedException e = (ChangesetClosedException)lastException;
+                    if (strategy.getPolicy() == null)
+                        /* do nothing if unknown policy */
+                        return;
+                    if (e.getSource().equals(ChangesetClosedException.Source.UPLOAD_DATA)) {
+                        switch(strategy.getPolicy()) {
+                        case ABORT:
+                            break; /* do nothing - we return to map editing */
+                        case AUTOMATICALLY_OPEN_NEW_CHANGESETS:
+                            break; /* do nothing - we return to map editing */
+                        case FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG:
+                            // return to the upload dialog
+                            //
+                            toUpload.removeProcessed(processedPrimitives);
+                            UploadDialog.getUploadDialog().setUploadedPrimitives(toUpload);
+                            UploadDialog.getUploadDialog().setVisible(true);
+                            break;
+                        }
+                    } else {
+                        handleFailedUpload(lastException);
+                    }
+                } else {
+                    handleFailedUpload(lastException);
+                }
+
+            }
+        };
+        SwingUtilities.invokeLater(r);
+    }
+
+    @Override protected void cancel() {
+        uploadCancelled = true;
+        synchronized(this) {
+            if (writer != null) {
+                writer.cancel();
+            }
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java	(revision 2599)
@@ -9,6 +9,10 @@
 import java.awt.GridBagLayout;
 import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
@@ -23,18 +27,38 @@
 import javax.swing.JTextField;
 import javax.swing.UIManager;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 
 import org.openstreetmap.josm.Main;
-
-public class UploadStrategySelectionPanel extends JPanel {
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * UploadStrategySelectionPanel is a panel for selecting an upload strategy.
+ * 
+ * Clients can listen for property change events for the property
+ * {@see #UPLOAD_STRATEGY_SPECIFICATION_PROP}.
+ */
+public class UploadStrategySelectionPanel extends JPanel implements PropertyChangeListener {
+
+    /**
+     * The property for the upload strategy
+     */
+    public final static String UPLOAD_STRATEGY_SPECIFICATION_PROP =
+        UploadStrategySelectionPanel.class.getName() + ".uploadStrategySpecification";
+
     private static final Color BG_COLOR_ERROR = new Color(255,224,224);
 
     private ButtonGroup bgStrategies;
+    private ButtonGroup bgMultiChangesetPolicies;
     private Map<UploadStrategy, JRadioButton> rbStrategy;
     private Map<UploadStrategy, JLabel> lblNumRequests;
+    private Map<UploadStrategy, JMultilineLabel> lblStrategies;
     private JTextField tfChunkSize;
+    private JPanel pnlMultiChangesetPolicyPanel;
+    private JRadioButton rbFillOneChangeset;
+    private JRadioButton rbUseMultipleChangesets;
+    private JMultilineLabel lblMultiChangesetPoliciesHeader;
 
     private long numUploadedObjects = 0;
@@ -44,12 +68,15 @@
     }
 
-    protected void build() {
-        setLayout(new GridBagLayout());
+    protected JPanel buildUploadStrategyPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
         bgStrategies = new ButtonGroup();
         rbStrategy = new HashMap<UploadStrategy, JRadioButton>();
+        lblStrategies = new HashMap<UploadStrategy, JMultilineLabel>();
         lblNumRequests = new HashMap<UploadStrategy, JLabel>();
         for (UploadStrategy strategy: UploadStrategy.values()) {
             rbStrategy.put(strategy, new JRadioButton());
             lblNumRequests.put(strategy, new JLabel());
+            lblStrategies.put(strategy, new JMultilineLabel(""));
             bgStrategies.add(rbStrategy.get(strategy));
         }
@@ -59,40 +86,27 @@
         gc.gridx = 0;
         gc.gridy = 0;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.insets = new Insets(0,5,0,5);
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.gridwidth = 4;
+        gc.insets = new Insets(0,0,3,0);
         gc.anchor = GridBagConstraints.FIRST_LINE_START;
-        add(rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc);
-        gc.gridx = 1;
-        gc.gridy = 0;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.gridwidth = 2;
-        add(new JLabel(tr("Upload data in one request")), gc);
-        gc.gridx = 3;
-        gc.gridy = 0;
-        gc.weightx = 1.0;
-        gc.weighty = 0.0;
-        gc.gridwidth = 1;
-        add(lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc);
-
-        // -- chunked dataset strategy
+        pnl.add(new JMultilineLabel(tr("Please select the upload strategy:")), gc);
+
+        // -- single request strategy
         gc.gridx = 0;
         gc.gridy = 1;
         gc.weightx = 0.0;
         gc.weighty = 0.0;
-        add(rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc);
+        gc.gridwidth = 1;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        pnl.add(rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc);
         gc.gridx = 1;
         gc.gridy = 1;
         gc.weightx = 0.0;
         gc.weighty = 0.0;
-        gc.gridwidth = 1;
-        add(new JLabel(tr("Upload data in chunks of objects. Chunk size: ")), gc);
-        gc.gridx = 2;
-        gc.gridy = 1;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        gc.gridwidth = 1;
-        add(tfChunkSize = new JTextField(4), gc);
+        gc.gridwidth = 2;
+        JLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY);
+        lbl.setText(tr("Upload data in one request"));
+        pnl.add(lbl, gc);
         gc.gridx = 3;
         gc.gridy = 1;
@@ -100,33 +114,114 @@
         gc.weighty = 0.0;
         gc.gridwidth = 1;
-        add(lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc);
+        pnl.add(lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY), gc);
+
+        // -- chunked dataset strategy
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        pnl.add(rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc);
+        gc.gridx = 1;
+        gc.gridy = 2;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        gc.gridwidth = 1;
+        lbl = lblStrategies.get(UploadStrategy.CHUNKED_DATASET_STRATEGY);
+        lbl.setText(tr("Upload data in chunks of objects. Chunk size: "));
+        pnl.add(lbl, gc);
+        gc.gridx = 2;
+        gc.gridy = 2;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        gc.gridwidth = 1;
+        pnl.add(tfChunkSize = new JTextField(4), gc);
+        gc.gridx = 3;
+        gc.gridy = 2;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.gridwidth = 1;
+        pnl.add(lblNumRequests.get(UploadStrategy.CHUNKED_DATASET_STRATEGY), gc);
 
         // -- single request strategy
         gc.gridx = 0;
-        gc.gridy = 2;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        add(rbStrategy.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc);
+        gc.gridy = 3;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        pnl.add(rbStrategy.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc);
         gc.gridx = 1;
-        gc.gridy = 2;
+        gc.gridy = 3;
         gc.weightx = 0.0;
         gc.weighty = 0.0;
         gc.gridwidth = 2;
-        add(new JLabel(tr("Upload each object individually")), gc);
+        lbl = lblStrategies.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY);
+        lbl.setText(tr("Upload each object individually"));
+        pnl.add(lbl, gc);
         gc.gridx = 3;
-        gc.gridy = 2;
-        gc.weightx = 1.0;
-        gc.weighty = 0.0;
-        gc.gridwidth = 1;
-        add(lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc);
-
+        gc.gridy = 3;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        gc.gridwidth = 1;
+        pnl.add(lblNumRequests.get(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY), gc);
 
         tfChunkSize.addFocusListener(new TextFieldFocusHandler());
         tfChunkSize.getDocument().addDocumentListener(new ChunkSizeInputVerifier());
+
         StrategyChangeListener strategyChangeListener = new StrategyChangeListener();
+        tfChunkSize.addFocusListener(strategyChangeListener);
+        tfChunkSize.addActionListener(strategyChangeListener);
         for(UploadStrategy strategy: UploadStrategy.values()) {
-            rbStrategy.get(strategy).addChangeListener(strategyChangeListener);
-        }
-
+            rbStrategy.get(strategy).addItemListener(strategyChangeListener);
+        }
+
+        return pnl;
+    }
+
+    protected JPanel buildMultiChangesetPolicyPanel() {
+        pnlMultiChangesetPolicyPanel = new JPanel();
+        pnlMultiChangesetPolicyPanel.setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        gc.weightx = 1.0;
+        pnlMultiChangesetPolicyPanel.add(lblMultiChangesetPoliciesHeader = new JMultilineLabel(tr("<html>There are <strong>multiple changesets</strong> necessary in order to upload {0} objects. What policy shall be used?</html>", numUploadedObjects)), gc);
+        gc.gridy = 1;
+        pnlMultiChangesetPolicyPanel.add(rbFillOneChangeset = new JRadioButton(tr("Fill up one changeset and return to the Upload Dialog")),gc);
+        gc.gridy = 2;
+        pnlMultiChangesetPolicyPanel.add(rbUseMultipleChangesets = new JRadioButton(tr("Open and use as many new changesets as necessary")),gc);
+
+        bgMultiChangesetPolicies = new ButtonGroup();
+        bgMultiChangesetPolicies.add(rbFillOneChangeset);
+        bgMultiChangesetPolicies.add(rbUseMultipleChangesets);
+        return pnlMultiChangesetPolicyPanel;
+    }
+
+    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.anchor = GridBagConstraints.NORTHWEST;
+        gc.insets = new Insets(3,3,3,3);
+
+        add(buildUploadStrategyPanel(), gc);
+        gc.gridy = 1;
+        add(buildMultiChangesetPolicyPanel(), gc);
+
+        // consume remaining space
+        gc.gridy = 2;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        add(new JPanel(), gc);
+
+        int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangsetSize();
+        pnlMultiChangesetPolicyPanel.setVisible(
+                maxChunkSize > 0 && numUploadedObjects > maxChunkSize
+        );
     }
 
@@ -139,6 +234,6 @@
         if (strategy == null) return;
         rbStrategy.get(strategy.getStrategy()).setSelected(true);
+        tfChunkSize.setEnabled(strategy.equals(UploadStrategy.CHUNKED_DATASET_STRATEGY));
         if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)) {
-            tfChunkSize.setEnabled(strategy.equals(UploadStrategy.CHUNKED_DATASET_STRATEGY));
             if (strategy.getChunkSize() != UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
                 tfChunkSize.setText(Integer.toString(strategy.getChunkSize()));
@@ -152,11 +247,28 @@
         UploadStrategy strategy = getUploadStrategy();
         int chunkSize = getChunkSize();
+        UploadStrategySpecification spec = new UploadStrategySpecification();
         switch(strategy) {
-        case INDIVIDUAL_OBJECTS_STRATEGY: return UploadStrategySpecification.createIndividualObjectStrategy();
-        case SINGLE_REQUEST_STRATEGY: return UploadStrategySpecification.createSingleRequestUploadStrategy();
-        case CHUNKED_DATASET_STRATEGY: return UploadStrategySpecification.createChunkedUploadStrategy(chunkSize);
-        }
-        // should not happen
-        return null;
+        case INDIVIDUAL_OBJECTS_STRATEGY:
+            spec.setStrategy(strategy);
+            break;
+        case SINGLE_REQUEST_STRATEGY:
+            spec.setStrategy(strategy);
+            break;
+        case CHUNKED_DATASET_STRATEGY:
+            spec.setStrategy(strategy).setChunkSize(chunkSize);
+            break;
+        }
+        if(pnlMultiChangesetPolicyPanel.isVisible()) {
+            if (rbFillOneChangeset.isSelected()) {
+                spec.setPolicy(MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG);
+            } else if (rbUseMultipleChangesets.isSelected()) {
+                spec.setPolicy(MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS);
+            } else {
+                spec.setPolicy(null); // unknown policy
+            }
+        } else {
+            spec.setPolicy(null);
+        }
+        return spec;
     }
 
@@ -171,5 +283,4 @@
         return strategy;
     }
-
 
     protected int getChunkSize() {
@@ -191,5 +302,5 @@
     }
 
-    public void saveToPreferences() {
+    public void rememberUserInput() {
         UploadStrategy strategy = getUploadStrategy();
         UploadStrategy.saveToPreferences(strategy);
@@ -204,4 +315,37 @@
 
     protected void updateNumRequestsLabels() {
+        int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangsetSize();
+        if (maxChunkSize > 0 && numUploadedObjects > maxChunkSize) {
+            rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setEnabled(false);
+            JLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY);
+            lbl.setIcon(ImageProvider.get("warning-small.png"));
+            lbl.setText(tr("Upload in one request not possible (too many objects to upload)"));
+            lbl.setToolTipText(tr("<html>Can''t upload {0} objects in one request because the<br>"
+                    + "max. changeset size {1} on server ''{2}'' is exceeded.</html>",
+                    numUploadedObjects,
+                    maxChunkSize,
+                    OsmApi.getOsmApi().getBaseUrl()
+            )
+            );
+            rbStrategy.get(UploadStrategy.CHUNKED_DATASET_STRATEGY).setSelected(true);
+            lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setVisible(false);
+
+            lblMultiChangesetPoliciesHeader.setText(tr("<html>There are <strong>multiple changesets</strong> necessary in order to upload {0} objects. What policy shall be used?</html>", numUploadedObjects));
+            if (!rbFillOneChangeset.isSelected() && ! rbUseMultipleChangesets.isSelected()) {
+                rbUseMultipleChangesets.setSelected(true);
+            }
+            pnlMultiChangesetPolicyPanel.setVisible(true);
+
+        } else {
+            rbStrategy.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setEnabled(true);
+            JLabel lbl = lblStrategies.get(UploadStrategy.SINGLE_REQUEST_STRATEGY);
+            lbl.setText(tr("Upload data in one request"));
+            lbl.setIcon(null);
+            lbl.setToolTipText("");
+            lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setVisible(true);
+
+            pnlMultiChangesetPolicyPanel.setVisible(false);
+        }
+
         lblNumRequests.get(UploadStrategy.SINGLE_REQUEST_STRATEGY).setText(tr("(1 request)"));
         if (numUploadedObjects == 0) {
@@ -229,4 +373,10 @@
     }
 
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(UploadedObjectsSummaryPanel.NUM_OBJECTS_TO_UPLOAD_PROP)) {
+            setNumUploadedObjects((Integer)evt.getNewValue());
+        }
+    }
+
     class TextFieldFocusHandler implements FocusListener {
         public void focusGained(FocusEvent e) {
@@ -241,5 +391,4 @@
 
     class ChunkSizeInputVerifier implements DocumentListener, PropertyChangeListener {
-
         protected void setErrorFeedback(JTextField tf, String message) {
             tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
@@ -257,8 +406,15 @@
             try {
                 int chunkSize = Integer.parseInt(tfChunkSize.getText().trim());
+                int maxChunkSize = OsmApi.getOsmApi().getCapabilities().getMaxChangsetSize();
                 if (chunkSize <= 0) {
                     setErrorFeedback(tfChunkSize, tr("Illegal chunk size <= 0. Please enter an integer > 1"));
+                } else if (maxChunkSize > 0 && chunkSize > maxChunkSize) {
+                    setErrorFeedback(tfChunkSize, tr("Chunk size {0} exceeds max. changeset size {1} for server ''{2}''", chunkSize, maxChunkSize, OsmApi.getOsmApi().getBaseUrl()));
                 } else {
                     clearErrorFeedback(tfChunkSize, tr("Please enter an integer > 1"));
+                }
+
+                if (maxChunkSize > 0 && chunkSize > maxChunkSize) {
+                    setErrorFeedback(tfChunkSize, tr("Chunk size {0} exceeds max. changeset size {1} for server ''{2}''", chunkSize, maxChunkSize, OsmApi.getOsmApi().getBaseUrl()));
                 }
             } catch(NumberFormatException e) {
@@ -291,6 +447,11 @@
     }
 
-    class StrategyChangeListener implements ChangeListener {
-        public void stateChanged(ChangeEvent e) {
+    class StrategyChangeListener implements ItemListener, FocusListener, ActionListener {
+
+        protected void notifyStrategy() {
+            firePropertyChange(UPLOAD_STRATEGY_SPECIFICATION_PROP, null, getUploadStrategySpecification());
+        }
+
+        public void itemStateChanged(ItemEvent e) {
             UploadStrategy strategy = getUploadStrategy();
             if (strategy == null) return;
@@ -303,4 +464,15 @@
                 tfChunkSize.setEnabled(false);
             }
+            notifyStrategy();
+        }
+
+        public void focusGained(FocusEvent arg0) {}
+
+        public void focusLost(FocusEvent arg0) {
+            notifyStrategy();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            notifyStrategy();
         }
     }
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySpecification.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySpecification.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadStrategySpecification.java	(revision 2599)
@@ -2,31 +2,146 @@
 package org.openstreetmap.josm.gui.io;
 
-public class UploadStrategySpecification {
+
+
+/**
+ * An UploadStrategySpecification consists of the parameter describing the strategy
+ * for uploading a collection of {@see OsmPrimitive}.
+ * 
+ * This includes:
+ * <ul>
+ * <li>a decision on which {@see UploadStrategy} to use</li>
+ * <li>the upload chunk size</li>
+ * <li>whether to close the changeset used after the upload</li>
+ * </ul>
+ *
+ *
+ */
+public class UploadStrategySpecification  {
+    /** indicates that the chunk size isn't specified */
     static public final int UNSPECIFIED_CHUNK_SIZE = -1;
-    static public UploadStrategySpecification createIndividualObjectStrategy() {
-        return new UploadStrategySpecification(UploadStrategy.INDIVIDUAL_OBJECTS_STRATEGY, 1);
-    }
-
-    static public UploadStrategySpecification createChunkedUploadStrategy(int chunkSize) {
-        return new UploadStrategySpecification(UploadStrategy.CHUNKED_DATASET_STRATEGY, chunkSize);
-    }
-
-    static public UploadStrategySpecification createSingleRequestUploadStrategy() {
-        return new UploadStrategySpecification(UploadStrategy.SINGLE_REQUEST_STRATEGY, UNSPECIFIED_CHUNK_SIZE);
-    }
 
     private UploadStrategy strategy;
     private int chunkSize;
+    private MaxChangesetSizeExceededPolicy policy;
+    private boolean closeChangesetAfterUpload;
 
-    private UploadStrategySpecification(UploadStrategy strategy, int chunkSize) {
-        this.strategy = strategy;
-        this.chunkSize = chunkSize;
+    /**
+     * Creates a new upload strategy with default values.
+     */
+    public UploadStrategySpecification() {
+        this.strategy = UploadStrategy.DEFAULT_UPLOAD_STRATEGY;
+        this.chunkSize = UNSPECIFIED_CHUNK_SIZE;
+        this.policy = null;
+        this.closeChangesetAfterUpload = true;
     }
 
+    /**
+     * Clones another upload strategy. If other is null,assumes default
+     * values.
+     * 
+     * @param other the other upload strategy
+     */
+    public UploadStrategySpecification(UploadStrategySpecification other) {
+        if (other == null) return;
+        this.strategy = other.strategy;
+        this.chunkSize = other.chunkSize;
+        this.policy = other.policy;
+        this.closeChangesetAfterUpload = other.closeChangesetAfterUpload;
+    }
+
+    /**
+     * Replies the upload strategy
+     * @return
+     */
     public UploadStrategy getStrategy() {
         return strategy;
     }
+
     public int getChunkSize() {
         return chunkSize;
     }
+
+    public static int getUnspecifiedChunkSize() {
+        return UNSPECIFIED_CHUNK_SIZE;
+    }
+
+    public MaxChangesetSizeExceededPolicy getPolicy() {
+        return policy;
+    }
+
+    public UploadStrategySpecification setStrategy(UploadStrategy strategy) {
+        this.strategy = strategy;
+        return this;
+    }
+
+    public UploadStrategySpecification setChunkSize(int chunkSize) {
+        this.chunkSize = chunkSize;
+        return this;
+    }
+
+    public UploadStrategySpecification setPolicy(MaxChangesetSizeExceededPolicy policy) {
+        this.policy = policy;
+        return this;
+    }
+
+    public UploadStrategySpecification setCloseChangesetAfterUpload(boolean closeChangesetAfterUpload) {
+        this.closeChangesetAfterUpload = closeChangesetAfterUpload;
+        return this;
+    }
+
+    public boolean isCloseChangesetAfterUpload() {
+        return closeChangesetAfterUpload;
+    }
+
+    public int getNumRequests(int numObjects) {
+        if (numObjects <=0) return 0;
+        switch(strategy) {
+        case INDIVIDUAL_OBJECTS_STRATEGY: return numObjects;
+        case SINGLE_REQUEST_STRATEGY: return 1;
+        case CHUNKED_DATASET_STRATEGY:
+            if (chunkSize == UNSPECIFIED_CHUNK_SIZE)
+                return 0;
+            else
+                return (int)Math.ceil((double)numObjects / (double)chunkSize);
+        }
+        // should not happen
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + chunkSize;
+        result = prime * result + (closeChangesetAfterUpload ? 1231 : 1237);
+        result = prime * result + ((policy == null) ? 0 : policy.hashCode());
+        result = prime * result + ((strategy == null) ? 0 : strategy.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        UploadStrategySpecification other = (UploadStrategySpecification) obj;
+        if (chunkSize != other.chunkSize)
+            return false;
+        if (closeChangesetAfterUpload != other.closeChangesetAfterUpload)
+            return false;
+        if (policy == null) {
+            if (other.policy != null)
+                return false;
+        } else if (!policy.equals(other.policy))
+            return false;
+        if (strategy == null) {
+            if (other.strategy != null)
+                return false;
+        } else if (!strategy.equals(other.strategy))
+            return false;
+        return true;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java	(revision 2599)
@@ -0,0 +1,186 @@
+// 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.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+
+/**
+ * This panel displays a summary of the objects to upload. It is displayed in
+ * the upper part of the {@see UploadDialog}.
+ * 
+ */
+public class UploadedObjectsSummaryPanel extends JPanel {
+    static public final String NUM_OBJECTS_TO_UPLOAD_PROP = UploadedObjectsSummaryPanel.class.getName() + ".numObjectsToUpload";
+
+    /** the list with the added primitives */
+    private PrimitiveList lstAdd;
+    private JLabel lblAdd;
+    private JScrollPane spAdd;
+    /** the list with the updated primitives */
+    private PrimitiveList lstUpdate;
+    private JLabel lblUpdate;
+    private JScrollPane spUpdate;
+    /** the list with the deleted primitives */
+    private PrimitiveList lstDelete;
+    private JLabel lblDelete;
+    private JScrollPane spDelete;
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
+        // initialize the three lists for uploaded primitives, but don't add
+        // them to the dialog yet,  see setUploadedPrimitives()
+        //
+        lstAdd = new PrimitiveList();
+        lstAdd.setCellRenderer(renderer);
+        lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
+        spAdd = new JScrollPane(lstAdd);
+        lblAdd = new JLabel(tr("Objects to add:"));
+
+        lstUpdate = new PrimitiveList();
+        lstUpdate.setCellRenderer(renderer);
+        lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
+        spUpdate = new JScrollPane(lstUpdate);
+        lblUpdate = new JLabel(tr("Objects to modify:"));
+
+        lstDelete = new PrimitiveList();
+        lstDelete.setCellRenderer(renderer);
+        lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
+        spDelete = new JScrollPane(lstDelete);
+        lblDelete = new JLabel(tr("Objects to delete:"));
+    }
+
+    /**
+     * Sets the collections of primitives which will be uploaded
+     *
+     * @param add  the collection of primitives to add
+     * @param update the collection of primitives to update
+     * @param delete the collection of primitives to delete
+     */
+    public void setUploadedPrimitives(List<OsmPrimitive> add, List<OsmPrimitive> update, List<OsmPrimitive> delete) {
+        lstAdd.getPrimitiveListModel().setPrimitives(add);
+        lstUpdate.getPrimitiveListModel().setPrimitives(update);
+        lstDelete.getPrimitiveListModel().setPrimitives(delete);
+
+        GridBagConstraints gcLabel = new GridBagConstraints();
+        gcLabel.fill = GridBagConstraints.HORIZONTAL;
+        gcLabel.weightx = 1.0;
+        gcLabel.weighty = 0.0;
+        gcLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+
+        GridBagConstraints gcList = new GridBagConstraints();
+        gcList.fill = GridBagConstraints.BOTH;
+        gcList.weightx = 1.0;
+        gcList.weighty = 1.0;
+        gcList.anchor = GridBagConstraints.CENTER;
+        removeAll();
+        int y = -1;
+        if (!add.isEmpty()) {
+            y++;
+            gcLabel.gridy = y;
+            lblAdd.setText(trn("{0} object to add:", "{0} objects to add:", add.size(),add.size()));
+            add(lblAdd, gcLabel);
+            y++;
+            gcList.gridy = y;
+            add(spAdd, gcList);
+        }
+        if (!update.isEmpty()) {
+            y++;
+            gcLabel.gridy = y;
+            lblUpdate.setText(trn("{0} object to modify:", "{0} objects to modify:", update.size(),update.size()));
+            add(lblUpdate, gcLabel);
+            y++;
+            gcList.gridy = y;
+            add(spUpdate, gcList);
+        }
+        if (!delete.isEmpty()) {
+            y++;
+            gcLabel.gridy = y;
+            lblDelete.setText(trn("{0} object to delete:", "{0} objects to delete:", delete.size(),delete.size()));
+            add(lblDelete, gcLabel);
+            y++;
+            gcList.gridy = y;
+            add(spDelete, gcList);
+        }
+
+        firePropertyChange(NUM_OBJECTS_TO_UPLOAD_PROP,0, getNumObjectsToUpload());
+    }
+
+    public UploadedObjectsSummaryPanel() {
+        build();
+    }
+
+    /**
+     * Replies the number of objects to upload
+     * 
+     * @return the number of objects to upload
+     */
+    public int  getNumObjectsToUpload() {
+        return lstAdd.getModel().getSize()
+        + lstUpdate.getModel().getSize()
+        + lstDelete.getModel().getSize();
+    }
+
+    /**
+     * A simple list of OSM primitives.
+     *
+     */
+    class PrimitiveList extends JList {
+        public PrimitiveList() {
+            super(new PrimitiveListModel());
+        }
+
+        public PrimitiveListModel getPrimitiveListModel() {
+            return (PrimitiveListModel)getModel();
+        }
+    }
+
+    /**
+     * A list model for a list of OSM primitives.
+     *
+     */
+    class PrimitiveListModel extends AbstractListModel{
+        private List<OsmPrimitive> primitives;
+
+        public PrimitiveListModel() {
+            primitives = new ArrayList<OsmPrimitive>();
+        }
+
+        public PrimitiveListModel(List<OsmPrimitive> primitives) {
+            setPrimitives(primitives);
+        }
+
+        public void setPrimitives(List<OsmPrimitive> primitives) {
+            if (primitives == null) {
+                this.primitives = new ArrayList<OsmPrimitive>();
+            } else {
+                this.primitives = primitives;
+            }
+            fireContentsChanged(this,0,getSize());
+        }
+
+        public Object getElementAt(int index) {
+            if (primitives == null) return null;
+            return primitives.get(index);
+        }
+
+        public int getSize() {
+            if (primitives == null) return 0;
+            return primitives.size();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/TagCellEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TagCellEditor.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/TagCellEditor.java	(revision 2599)
@@ -155,5 +155,4 @@
             }
         }
-
         return super.stopCellEditing();
     }
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java	(revision 2599)
@@ -219,12 +219,16 @@
         if (name == null) return;
         Iterator<TagModel> it = tags.iterator();
+        boolean changed = false;
         while(it.hasNext()) {
             TagModel tm = it.next();
             if (tm.getName().equals(name)) {
+                changed = true;
                 it.remove();
             }
         }
-        fireTableDataChanged();
-        setDirty(true);
+        if (changed) {
+            fireTableDataChanged();
+            setDirty(true);
+        }
     }
     /**
@@ -449,4 +453,5 @@
             setDirty(true);
         }
+        fireTableDataChanged();
     }
 
@@ -464,4 +469,5 @@
             setDirty(true);
         }
+        fireTableDataChanged();
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java	(revision 2599)
@@ -7,4 +7,5 @@
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
+import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.beans.PropertyChangeEvent;
@@ -69,5 +70,7 @@
         //
         AddAction addAction = new AddAction();
-        pnl.add(new JButton(addAction));
+        JButton btn;
+        pnl.add(btn = new JButton(addAction));
+        btn.setMargin(new Insets(0,0,0,0));
         tagTable.addPropertyChangeListener(addAction);
 
@@ -77,5 +80,6 @@
         tagTable.getSelectionModel().addListSelectionListener(deleteAction);
         tagTable.addPropertyChangeListener(deleteAction);
-        pnl.add(new JButton(deleteAction));
+        pnl.add(btn = new JButton(deleteAction));
+        btn.setMargin(new Insets(0,0,0,0));
         return pnl;
     }
Index: /trunk/src/org/openstreetmap/josm/io/Capabilities.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/Capabilities.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/io/Capabilities.java	(revision 2599)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.util.HashMap;
@@ -71,3 +73,25 @@
     }
 
+    /**
+     * Replies the max number of objects in a changeset. -1 if either the capabilities
+     * don't include this parameter or if the parameter value is illegal (not a number,
+     * a negative number)
+     * 
+     * @return the max number of objects in a changeset
+     */
+    public int getMaxChangsetSize() {
+        String v = get("changesets", "maximum_elements");
+        if (v == null) return -1;
+        try {
+            int n = Integer.parseInt(v);
+            if (n <= 0) {
+                System.err.println(tr("Warning: illegal value of attribute '{0}'' of element ''{1}'' in server capabilities. Got ''{2}", "changesets", "maximum_elements", n ));
+                return -1;
+            }
+            return n;
+        } catch(NumberFormatException e) {
+            System.err.println(tr("Warning: illegal value of attribute '{0}'' of element ''{1}'' in server capabilities. Got ''{2}", "changesets", "maximum_elements", v ));
+            return -1;
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/io/ChangesetClosedException.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/ChangesetClosedException.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/io/ChangesetClosedException.java	(revision 2599)
@@ -31,5 +31,5 @@
     final static public String ERROR_HEADER_PATTERN = "The changeset (\\d+) was closed at (.*)";
 
-    static enum Source {
+    public static enum Source {
         /**
          * The exception was thrown when a changeset was updated. This most likely means
@@ -114,4 +114,18 @@
 
     /**
+     * Creates the exception
+     * 
+     * @param changesetId the id if the closed changeset
+     * @param closedOn the date the changeset was closed on
+     * @param source the source for the exception
+     */
+    public ChangesetClosedException(long changesetId, Date closedOn, Source source) {
+        super("");
+        this.source = source == null ? Source.UNSPECIFIED : source;
+        this.changesetId = changesetId;
+        this.closedOn = closedOn;
+    }
+
+    /**
      * Replies the id of the changeset which was closed
      * 
@@ -139,3 +153,7 @@
         return source;
     }
+
+    public void setSource(Source source) {
+        this.source = source == null ? Source.UNSPECIFIED : source;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 2599)
@@ -19,4 +19,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
@@ -76,20 +77,17 @@
 
     /**
-     * remembers an {@see OsmPrimitive}'s id and its type. The id will
+     * Remembers an {@see OsmPrimitive}'s id. The id will
      * later be fetched as part of a Multi Get request.
      *
-     * Ignore the id if it id <= 0.
+     * Ignore the id if it represents a new primitives.
      *
      * @param id  the id
-     * @param type  the type
-     */
-    protected void remember(long id, OsmPrimitiveType type) {
-        if (id <= 0) return;
-        if (type.equals(OsmPrimitiveType.NODE)) {
-            nodes.add(id);
-        } else if (type.equals(OsmPrimitiveType.WAY)) {
-            ways.add(id);
-        } if (type.equals(OsmPrimitiveType.RELATION)) {
-            relations.add(id);
+     */
+    protected void remember(PrimitiveId id) {
+        if (id.isNew()) return;
+        switch(id.getType()) {
+        case NODE: nodes.add(id.getUniqueId()); break;
+        case WAY: ways.add(id.getUniqueId()); break;
+        case RELATION: relations.add(id.getUniqueId()); break;
         }
     }
@@ -115,5 +113,5 @@
         if (primitive == null)
             throw new NoSuchElementException(tr("No primitive with id {0} in local dataset. Can't infer primitive type.", id));
-        remember(id, OsmPrimitiveType.from(primitive));
+        remember(primitive.getPrimitiveId());
         return;
     }
@@ -153,6 +151,5 @@
     public MultiFetchServerObjectReader append(Node node) {
         if (node == null) return this;
-        if (node.isNew()) return this;
-        remember(node.getId(), OsmPrimitiveType.NODE);
+        remember(node.getPrimitiveId());
         return this;
     }
@@ -170,8 +167,8 @@
         for (Node node: way.getNodes()) {
             if (!node.isNew()) {
-                remember(node.getId(), OsmPrimitiveType.NODE);
-            }
-        }
-        remember(way.getId(), OsmPrimitiveType.WAY);
+                remember(node.getPrimitiveId());
+            }
+        }
+        remember(way.getPrimitiveId());
         return this;
     }
@@ -187,5 +184,5 @@
         if (relation == null) return this;
         if (relation.isNew()) return this;
-        remember(relation.getId(), OsmPrimitiveType.RELATION);
+        remember(relation.getPrimitiveId());
         for (RelationMember member : relation.getMembers()) {
             if (OsmPrimitiveType.from(member.getMember()).equals(OsmPrimitiveType.RELATION)) {
Index: /trunk/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2599)
@@ -80,5 +80,5 @@
         String serverUrl = Main.pref.get("osm-server.url", "http://api.openstreetmap.org/api");
         if (serverUrl == null)
-            throw new IllegalStateException(tr("Preference ''{0}'' missing. Can't initialize OsmApi.", "osm-server.url"));
+            throw new IllegalStateException(tr("Preference ''{0}'' missing. Can''t initialize OsmApi.", "osm-server.url"));
         return getOsmApi(serverUrl);
     }
@@ -342,4 +342,7 @@
                     monitor
             );
+        } catch(ChangesetClosedException e) {
+            e.setSource(ChangesetClosedException.Source.UPDATE_CHANGESET);
+            throw e;
         } catch(OsmApiException e) {
             if (e.getResponseCode() == HttpURLConnection.HTTP_CONFLICT && ChangesetClosedException.errorHeaderMatchesPattern(e.getErrorHeader()))
Index: /trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java	(revision 2598)
+++ /trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java	(revision 2599)
@@ -149,5 +149,4 @@
             while(it.hasNext()) {
                 i++;
-                progressMonitor.setCustomText(tr("({0}/{1}) Uploading {2} objects...", i,numChunks,chunkSize));
                 if (canceled) return;
                 int j = 0;
@@ -158,4 +157,5 @@
                     chunk.add(it.next());
                 }
+                progressMonitor.setCustomText(tr("({0}/{1}) Uploading {2} objects...", i,numChunks,chunk.size()));
                 processed.addAll(api.uploadDiff(chunk, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)));
             }
Index: /trunk/src/org/openstreetmap/josm/tools/CheckParameterUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/CheckParameterUtil.java	(revision 2599)
+++ /trunk/src/org/openstreetmap/josm/tools/CheckParameterUtil.java	(revision 2599)
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
+
+/**
+ * This utility class provides a collection of static helper methods for checking
+ * parameters at run-time.
+ *
+ */
+public class CheckParameterUtil {
+
+    private CheckParameterUtil(){}
+
+
+    public static void ensureValidPrimitiveId(PrimitiveId id, String parameterName) throws IllegalArgumentException {
+        if (id == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", parameterName));
+        if (id.getUniqueId() <= 0)
+            throw new IllegalArgumentException(tr("Expected unique id > 0 for primitive id, got {0}", id.getUniqueId()));
+    }
+
+    public static void ensureValidVersion(long version, String parameterName) throws IllegalArgumentException {
+        if (version < 0)
+            throw new IllegalArgumentException(tr("Expected value of type long > 0 for parameter ''{0}'', got {1}", parameterName, version));
+    }
+
+    public static void ensureParameterNotNull(Object value, String parameterName) {
+        if (value == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", parameterName));
+    }
+
+    /**
+     * Ensures that <code>id</code> is non-null primitive id of type {@see OsmPrimitiveType#NODE}
+     * 
+     * @param id  the primitive  id
+     * @param parameterName the name of the parameter to be checked
+     * @throws IllegalArgumentException thrown if id is null
+     * @throws IllegalArgumentException thrown if id.getType() != NODE
+     */
+    public static void ensureValidNodeId(PrimitiveId id, String parameterName) throws IllegalArgumentException {
+        if (id == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", parameterName));
+        if (! id.getType().equals(OsmPrimitiveType.NODE))
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' of type node expected, got ''{1}''", parameterName, id.getType().getAPIName()));
+    }
+}
