Index: /trunk/src/org/openstreetmap/josm/data/osm/UserInfo.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/UserInfo.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/data/osm/UserInfo.java	(revision 6349)
@@ -22,5 +22,10 @@
     /** the list of preferred languages */
     private List<String> languages;
+    /** the number of unread messages */
+    private int unreadMessages;
 
+    /**
+     * Constructs a new {@code UserInfo}.
+     */
     public UserInfo() {
         id = 0;
@@ -71,3 +76,21 @@
         this.homeZoom = homeZoom;
     }
+
+    /**
+     * Replies the number of unread messages
+     * @return the number of unread messages
+     * @since 6349
+     */
+    public final int getUnreadMessages() {
+        return unreadMessages;
+    }
+
+    /**
+     * Sets the number of unread messages
+     * @param unreadMessages the number of unread messages
+     * @since 6349
+     */
+    public final void setUnreadMessages(int unreadMessages) {
+        this.unreadMessages = unreadMessages;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/ExceptionDialogUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/ExceptionDialogUtil.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/gui/ExceptionDialogUtil.java	(revision 6349)
@@ -20,4 +20,5 @@
 import org.openstreetmap.josm.io.IllegalDataException;
 import org.openstreetmap.josm.io.MissingOAuthAccessTokenException;
+import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.io.OsmApiException;
 import org.openstreetmap.josm.io.OsmApiInitializationException;
@@ -244,8 +245,4 @@
     }
 
-    private static boolean isOAuth() {
-        return Main.pref.get("osm-server.auth-method", "basic").equals("oauth");
-    }
-
     /**
      * Explains a {@link OsmApiException} which was thrown because the authentication at
@@ -256,5 +253,5 @@
     public static void explainAuthenticationFailed(OsmApiException e) {
         String msg;
-        if (isOAuth()) {
+        if (OsmApi.isUsingOAuth()) {
             msg = ExceptionUtil.explainFailedOAuthAuthentication(e);
         } else {
@@ -293,5 +290,5 @@
             msg = tr("Access to redacted version ''{0}'' of {1} {2} is forbidden.",
                     version, tr(type), id);
-        } else if (isOAuth()) {
+        } else if (OsmApi.isUsingOAuth()) {
             msg = ExceptionUtil.explainFailedOAuthAuthorisation(e);
         } else {
Index: /trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java	(revision 6349)
@@ -14,4 +14,5 @@
 import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.io.OsmServerUserInfoReader;
 import org.openstreetmap.josm.io.OsmTransferException;
@@ -60,5 +61,5 @@
         if (instance == null) {
             instance = new JosmUserIdentityManager();
-            if (Main.pref.get("osm-server.auth-method").equals("oauth") && OAuthAccessTokenHolder.getInstance().containsAccessToken()) {
+            if (OsmApi.isUsingOAuth() && OAuthAccessTokenHolder.getInstance().containsAccessToken()) {
                 try {
                     instance.initFromOAuth(Main.parent);
@@ -272,5 +273,5 @@
             accessTokenKeyChanged = false;
             accessTokenSecretChanged = false;
-            if (Main.pref.get("osm-server.auth-method").equals("oauth")) {
+            if (OsmApi.isUsingOAuth()) {
                 try {
                     instance.initFromOAuth(Main.parent);
Index: /trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 6349)
@@ -42,4 +42,5 @@
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.io.DefaultProxySelector;
+import org.openstreetmap.josm.io.MessageNotifier;
 import org.openstreetmap.josm.io.auth.CredentialsManager;
 import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
@@ -443,4 +444,8 @@
             RemoteControl.start();
         }
+        
+        if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
+            MessageNotifier.start();
+        }
 
         if (Main.pref.getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) {
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java	(revision 6349)
@@ -41,4 +41,6 @@
     /** the panel for the OAuth authentication parameters */
     private OAuthAuthenticationPreferencesPanel pnlOAuthPreferences;
+    /** the panel for messages notifier preferences */
+    private MessagesNotifierPanel pnlMessagesPreferences;
 
     /**
@@ -74,5 +76,5 @@
         bg.add(rbOAuth);
 
-        //-- add the panel which will hld the authentication parameters
+        //-- add the panel which will hold the authentication parameters
         gc.gridx = 0;
         gc.gridy = 1;
@@ -84,5 +86,5 @@
         pnlAuthenticationParameteters.setLayout(new BorderLayout());
 
-        //-- the two panel for authentication parameters
+        //-- the two panels for authentication parameters
         pnlBasicAuthPreferences = new BasicAuthenticationPreferencesPanel();
         pnlOAuthPreferences = new OAuthAuthenticationPreferencesPanel();
@@ -90,6 +92,14 @@
         rbBasicAuthentication.setSelected(true);
         pnlAuthenticationParameteters.add(pnlBasicAuthPreferences, BorderLayout.CENTER);
+        
+        //-- the panel for messages preferences
+        gc.gridy = 2;
+        gc.fill = GridBagConstraints.NONE;
+        add(pnlMessagesPreferences = new MessagesNotifierPanel(), gc);
     }
 
+    /**
+     * Constructs a new {@code AuthenticationPreferencesPanel}.
+     */
     public AuthenticationPreferencesPanel() {
         build();
@@ -97,4 +107,7 @@
     }
 
+    /**
+     * Initializes the panel from preferences
+     */
     public void initFromPreferences() {
         String authMethod = Main.pref.get("osm-server.auth-method", "basic");
@@ -109,6 +122,10 @@
         pnlBasicAuthPreferences.initFromPreferences();
         pnlOAuthPreferences.initFromPreferences();
+        pnlMessagesPreferences.initFromPreferences();
     }
 
+    /**
+     * Saves the current values to preferences
+     */
     public void saveToPreferences() {
         // save the authentication method
@@ -131,4 +148,6 @@
             pnlOAuthPreferences.saveToPreferences();
         }
+        // save message notifications preferences. To be done after authentication preferences.
+        pnlMessagesPreferences.saveToPreferences();
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/server/MessagesNotifierPanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/server/MessagesNotifierPanel.java	(revision 6349)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/server/MessagesNotifierPanel.java	(revision 6349)
@@ -0,0 +1,98 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.preferences.server;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.io.MessageNotifier;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Preferences panel for OSM messages notifier.
+ * @since 6349
+ */
+public class MessagesNotifierPanel extends JPanel {
+
+    private JCheckBox notifier;
+    private JLabel intervalLabel;
+    private final JosmTextField notifierInterval = new JosmTextField(4);
+
+    /**
+     * Constructs a new {@code MessagesNotifierPanel}.
+     */
+    public MessagesNotifierPanel() {
+        build();
+        initFromPreferences();
+        updateEnabledState();
+    }
+
+    private void build() {
+        setLayout(new GridBagLayout());
+        setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+        notifier = new JCheckBox(tr("Periodically check for new messages"));
+        add(notifier, GBC.eol());
+        notifier.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                updateEnabledState();
+            }
+        });
+        
+        intervalLabel = new JLabel(tr("Check interval (minutes):"));
+        add(intervalLabel, GBC.std().insets(25,0,0,0));
+
+        notifierInterval.setToolTipText(tr("Default value: {0}", MessageNotifier.PROP_INTERVAL.getDefaultValue()));
+        notifierInterval.setMinimumSize(notifierInterval.getPreferredSize());
+        add(notifierInterval, GBC.eol().insets(5,0,0,0));
+    }
+    
+    private void updateEnabledState() {
+        boolean enabled = notifier.isSelected();
+        intervalLabel.setEnabled(enabled);
+        notifierInterval.setEnabled(enabled);
+        notifierInterval.setEditable(enabled);
+    }
+
+    /**
+     * Initializes the panel from preferences
+     */
+    public void initFromPreferences() {
+        notifier.setSelected(MessageNotifier.PROP_NOTIFIER_ENABLED.get());
+        notifierInterval.setText(Integer.toString(MessageNotifier.PROP_INTERVAL.get()));
+    }
+
+    /**
+     * Saves the current values to preferences
+     */
+    public void saveToPreferences() {
+        final boolean enabled = notifier.isSelected();
+        boolean changed = MessageNotifier.PROP_NOTIFIER_ENABLED.put(enabled);
+        changed |= MessageNotifier.PROP_INTERVAL.parseAndPut(notifierInterval.getText());
+        // If parameters have changed, restart notifier
+        if (changed) {
+            MessageNotifier.stop();
+            if (enabled) {
+                MessageNotifier.start();
+            }
+        // Even if they have not changed,
+        } else {
+            // notifier should be stopped if user is no more identified enough 
+            if (!MessageNotifier.isUserEnoughIdentified()) {
+                MessageNotifier.stop();
+            // or restarted if user is again identified and notifier was enabled in preferences
+            } else if (enabled && !MessageNotifier.isRunning()) {
+                MessageNotifier.start();
+            }
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/MessageNotifier.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/MessageNotifier.java	(revision 6349)
+++ /trunk/src/org/openstreetmap/josm/io/MessageNotifier.java	(revision 6349)
@@ -0,0 +1,146 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.GridBagLayout;
+import java.net.Authenticator.RequestorType;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.UserInfo;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.Notification;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.widgets.UrlLabel;
+import org.openstreetmap.josm.io.auth.CredentialsAgentException;
+import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
+import org.openstreetmap.josm.io.auth.CredentialsManager;
+import org.openstreetmap.josm.io.auth.JosmPreferencesCredentialAgent;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Notifies user periodically of new received (unread) messages
+ * @since 6349
+ */
+public final class MessageNotifier {
+
+    /** Property defining if this task is enabled or not */
+    public static final BooleanProperty PROP_NOTIFIER_ENABLED = new BooleanProperty("message.notifier.enabled", true);
+    /** Property defining the update interval in minutes */
+    public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("message.notifier.interval", 5);
+    
+    private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    
+    private static final Runnable worker = new Worker();
+    
+    private static ScheduledFuture<?> task = null;
+        
+    private static class Worker implements Runnable {
+
+        private int lastUnreadCount = 0;
+
+        @Override
+        public void run() {
+            try {
+                final UserInfo userInfo = new OsmServerUserInfoReader().fetchUserInfo(NullProgressMonitor.INSTANCE);
+                final int unread = userInfo.getUnreadMessages();
+                if (unread > 0 && unread != lastUnreadCount) {
+                    GuiHelper.runInEDT(new Runnable() {
+                        @Override
+                        public void run() {
+                            JPanel panel = new JPanel(new GridBagLayout());
+                            panel.add(new JLabel(trn("You have {0} unread message.", "You have {0} unread messages.", unread, unread)), GBC.eol());
+                            panel.add(new UrlLabel("http://www.openstreetmap.org/user/"+userInfo.getDisplayName()+"/inbox", tr("Click here to see your inbox.")), GBC.eol());
+                            panel.setOpaque(false);
+                            new Notification().setContent(panel)
+                                .setIcon(JOptionPane.INFORMATION_MESSAGE)
+                                .setDuration(Notification.TIME_LONG)
+                                .show();
+                        }
+                    });
+                    lastUnreadCount = unread;
+                }
+            } catch (OsmTransferException e) {
+                Main.warn(e);
+            }
+        }
+    }
+    
+    /**
+     * Starts the message notifier task if not already started and if user is fully identified
+     */
+    public static void start() {
+        int interval = PROP_INTERVAL.get();
+        if (!isRunning() && interval > 0 && isUserEnoughIdentified()) {
+            task = executor.scheduleAtFixedRate(worker, 0, interval * 60, TimeUnit.SECONDS);
+            Main.info("Message notifier active (checks every "+interval+" minute"+(interval>1?"s":"")+")");
+        }
+    }
+
+    /**
+     * Stops the message notifier task if started
+     */
+    public static void stop() {
+        if (isRunning()) {
+            task.cancel(false);
+            Main.info("Message notifier inactive");
+            task = null;
+        }
+    }
+    
+    /**
+     * Determines if the message notifier is currently running
+     * @return {@code true} if the notifier is running, {@code false} otherwise
+     */
+    public static boolean isRunning() {
+        return task != null;
+    }
+    
+    /**
+     * Determines if user set enough information in JOSM preferences to make the request to OSM API without
+     * prompting him for a password.
+     * @return {@code true} if user chose an OAuth token or supplied both its username and password, {@code false otherwise}
+     */
+    public static boolean isUserEnoughIdentified() {
+        JosmUserIdentityManager identManager = JosmUserIdentityManager.getInstance();
+        if (identManager.isFullyIdentified()) {
+            return true;
+        } else {
+            CredentialsManager credManager = CredentialsManager.getInstance();
+            try {
+                if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) {
+                    if (OsmApi.isUsingOAuth()) {
+                        return credManager.lookupOAuthAccessToken() != null;
+                    } else {
+                        String username = Main.pref.get("osm-server.username", null);
+                        String password = Main.pref.get("osm-server.password", null);
+                        return username != null && !username.isEmpty() && password != null && !password.isEmpty();
+                    }
+                } else {
+                    CredentialsAgentResponse credentials = credManager.getCredentials(
+                            RequestorType.SERVER, OsmApi.getOsmApi().getHost(), false);
+                    if (credentials != null) {
+                        String username = credentials.getUsername();
+                        char[] password = credentials.getPassword();
+                        return username != null && !username.isEmpty() && password != null && password.length > 0;
+                    }
+                }
+            } catch (CredentialsAgentException e) {
+                Main.warn("Unable to get credentials: "+e.getMessage());
+            }
+        }
+        return false;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 6349)
@@ -564,7 +564,11 @@
     }
 
-    protected boolean isUsingOAuth() {
-        String authMethod = Main.pref.get("osm-server.auth-method", "basic");
-        return authMethod.equals("oauth");
+    /**
+     * Determines if JOSM is configured to access OSM API via OAuth
+     * @return {@code true} if JOSM is configured to access OSM API via OAuth, {@code false} otherwise
+     * @since 6349
+     */
+    public static final boolean isUsingOAuth() {
+        return "oauth".equals(Main.pref.get("osm-server.auth-method", "basic"));
     }
 
Index: /trunk/src/org/openstreetmap/josm/io/OsmServerUserInfoReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmServerUserInfoReader.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/io/OsmServerUserInfoReader.java	(revision 6349)
@@ -29,5 +29,11 @@
     }
 
-    static public  UserInfo buildFromXML(Document document) throws OsmDataParsingException{
+    /**
+     * Parses the given XML data and returns the associated user info.
+     * @param document The XML contents
+     * @return The user info
+     * @throws OsmDataParsingException if parsing goes wrong
+     */
+    static public UserInfo buildFromXML(Document document) throws OsmDataParsingException {
         try {
             XPathFactory factory = XPathFactory.newInstance();
@@ -105,4 +111,18 @@
                 userInfo.setLanguages(languages);
             }
+            
+            // -- messages
+            xmlNode = (Node)xpath.compile("/osm/user[1]/messages/received").evaluate(document, XPathConstants.NODE);
+            if (xmlNode != null) {
+                v = getAttribute(xmlNode, "unread");
+                if (v == null)
+                    throw new OsmDataParsingException(tr("Missing attribute ''{0}'' on XML tag ''{1}''.", "unread", "received"));
+                try {
+                    userInfo.setUnreadMessages(Integer.parseInt(v));
+                } catch(NumberFormatException e) {
+                    throw new OsmDataParsingException(tr("Illegal value for attribute ''{0}'' on XML tag ''{1}''. Got {2}.", "unread", "received", v));
+                }
+            }
+            
             return userInfo;
         } catch(XPathException e) {
@@ -111,4 +131,7 @@
     }
 
+    /**
+     * Constructs a new {@code OsmServerUserInfoReader}.
+     */
     public OsmServerUserInfoReader() {
         setDoAuthenticate(true);
Index: /trunk/src/org/openstreetmap/josm/io/auth/CredentialsManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/auth/CredentialsManager.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/io/auth/CredentialsManager.java	(revision 6349)
@@ -9,4 +9,5 @@
 import org.openstreetmap.josm.gui.JosmUserIdentityManager;
 import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -15,5 +16,5 @@
  *
  * Currently, it defaults to replying an instance of {@link JosmPreferencesCredentialAgent}.
- *
+ * @since 2641
  */
 public class CredentialsManager implements CredentialsAgent {
@@ -56,18 +57,41 @@
     }
 
-    /*****
-     * non-static fields and methods
+    /* non-static fields and methods */
+
+    /**
+     * The credentials agent doing the real stuff
      */
-
     private CredentialsAgent delegate;
 
+    /**
+     * Constructs a new {@code CredentialsManager}.
+     * @param delegate The credentials agent backing this credential manager. Must not be {@code null}
+     */
     public CredentialsManager(CredentialsAgent delegate) {
+        CheckParameterUtil.ensureParameterNotNull(delegate, "delegate");
         this.delegate = delegate;
     }
+    
+    /**
+     * Returns type of credentials agent backing this credentials manager.
+     * @return The type of credentials agent
+     */
+    public final Class<? extends CredentialsAgent> getCredentialsAgentClass () {
+        return delegate.getClass();
+    }
 
+    /**
+     * Returns the username for OSM API
+     * @return the username for OSM API
+     */
     public String getUsername() {
         return getUsername(OsmApi.getOsmApi().getHost());
     }
 
+    /**
+     * Returns the username for a given host
+     * @param host The host for which username is wanted
+     * @return The username for {@code host}
+     */
     public String getUsername(String host) {
         String username = null;
Index: /trunk/src/org/openstreetmap/josm/io/auth/DefaultAuthenticator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/auth/DefaultAuthenticator.java	(revision 6348)
+++ /trunk/src/org/openstreetmap/josm/io/auth/DefaultAuthenticator.java	(revision 6349)
@@ -7,5 +7,5 @@
 import java.util.Map;
 
-import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.io.OsmApi;
 
 /**
@@ -43,7 +43,5 @@
             if (getRequestorType().equals(Authenticator.RequestorType.SERVER)) {
                 // if we are working with OAuth we don't prompt for a password
-                //
-                String authMethod = Main.pref.get("osm-server.auth-method", "basic");
-                if (authMethod.equals("oauth"))
+                if (OsmApi.isUsingOAuth())
                     return null;
             }
