geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rickmcgu...@apache.org
Subject svn commit: r761642 [2/2] - /geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
Date Fri, 03 Apr 2009 12:07:49 GMT

Modified: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java?rev=761642&r1=761641&r2=761642&view=diff
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java (original)
+++ geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java Fri Apr  3 12:07:48 2009
@@ -58,8 +58,8 @@
 
     /**
      * Special profile item used for fetching SIZE and HEADER information.
-     * These items are extensions that Sun has added to their IMAPFolder immplementation. 
-     * We're supporting the same set. 
+     * These items are extensions that Sun has added to their IMAPFolder immplementation.
+     * We're supporting the same set.
      */
     public static class FetchProfileItem extends FetchProfile.Item {
         public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
@@ -69,17 +69,17 @@
             super(name);
         }
     }
-    
-    // marker that we don't know the separator yet for this folder. 
-    // This occurs when we obtain a folder reference from the 
-    // default folder.  At that point, we've not queried the 
-    // server for specifics yet. 
-    static final protected char UNDETERMINED = 0; 
-    
+
+    // marker that we don't know the separator yet for this folder.
+    // This occurs when we obtain a folder reference from the
+    // default folder.  At that point, we've not queried the
+    // server for specifics yet.
+    static final protected char UNDETERMINED = 0;
+
     // our attached session
     protected Session session;
     // retrieved messages, mapped by sequence number.
-    protected LinkedList messages;
+    protected Map messageCache;
     // mappings of UIDs to retrieved messages.
     protected Map uidCache;
 
@@ -97,10 +97,10 @@
     // the subscription status
     protected boolean subscribed = false;
 
-    // the message identifier ticker, used to assign message numbers. 
-    protected int nextMessageID = 1; 
-    // the current count of messages in our cache. 
-    protected int maxSequenceNumber = 0;  
+    // the message identifier ticker, used to assign message numbers.
+    protected int nextMessageID = 1;
+    // the current count of messages in our cache.
+    protected int maxSequenceNumber = 0;
     // the reported count of new messages (updated as a result of untagged message resposes)
     protected int recentMessages = -1;
     // the reported count of unseen messages
@@ -115,26 +115,26 @@
     protected Flags availableFlags;
     // Our cached status information.  We will only hold this for the timeout interval.
     protected IMAPMailboxStatus cachedStatus;
-    // Folder information retrieved from the server.  Good info here indicates the 
-    // folder exists. 
-    protected IMAPListResponse listInfo; 
+    // Folder information retrieved from the server.  Good info here indicates the
+    // folder exists.
+    protected IMAPListResponse listInfo;
     // the configured status cache timeout value.
     protected long statusCacheTimeout;
     // the last time we took a status snap shot.
     protected long lastStatusTimeStamp;
-    // Our current connection.  We get one of these when opened, and release it when closed. 
-    // We do this because for any folder (and message) operations, the folder must be selected on 
-    // the connection.  
-    // Note, however, that there are operations which will require us to borrow a connection 
-    // temporarily because we need to touch the server when the folder is not open.  In those 
-    // cases, we grab a connection, then immediately return it to the pool. 
-    protected IMAPConnection currentConnection; 
-    
-    
+    // Our current connection.  We get one of these when opened, and release it when closed.
+    // We do this because for any folder (and message) operations, the folder must be selected on
+    // the connection.
+    // Note, however, that there are operations which will require us to borrow a connection
+    // temporarily because we need to touch the server when the folder is not open.  In those
+    // cases, we grab a connection, then immediately return it to the pool.
+    protected IMAPConnection currentConnection;
+
+
 
     /**
      * Super class constructor the base IMAPFolder class.
-     * 
+     *
      * @param store     The javamail store this folder is attached to.
      * @param fullname  The fully qualified name of this folder.
      * @param separator The separtor character used to delimit the different
@@ -147,29 +147,29 @@
 		this.session = store.getSession();
         this.fullname = fullname;
         this.separator = separator;
-        // get the status timeout value from the folder. 
-        statusCacheTimeout = store.statusCacheTimeout; 
+        // get the status timeout value from the folder.
+        statusCacheTimeout = store.statusCacheTimeout;
 	}
 
     /**
      * Retrieve the folder name.  This is the simple folder
      * name at the its hiearchy level.  This can be invoked when the folder is closed.
-     * 
+     *
      * @return The folder's name.
      */
 	public String getName() {
-        // At the time we create the folder, we might not know the separator character yet. 
-        // Because of this we need to delay creating the name element until 
-        // it's required.  
+        // At the time we create the folder, we might not know the separator character yet.
+        // Because of this we need to delay creating the name element until
+        // it's required.
         if (name == null) {
             // extract the name from the full name
-            int lastLevel = -1; 
+            int lastLevel = -1;
             try {
                 lastLevel = fullname.lastIndexOf(getSeparator());
             } catch (MessagingException e) {
-                // not likely to occur, but the link could go down before we 
-                // get this.  Just assume a failure to locate the character 
-                // occurred. 
+                // not likely to occur, but the link could go down before we
+                // get this.  Just assume a failure to locate the character
+                // occurred.
             }
             if (lastLevel == -1) {
                 name = fullname;
@@ -202,11 +202,11 @@
      * @throws MessagingException
      */
 	public Folder getParent() throws MessagingException {
-        // NB:  We need to use the method form because the separator 
-        // might not have been retrieved from the server yet. 
-        char separator = getSeparator(); 
-        // we don't hold a reference to the parent folder, as that would pin the instance in memory 
-        // as long as any any leaf item in the hierarchy is still open.  
+        // NB:  We need to use the method form because the separator
+        // might not have been retrieved from the server yet.
+        char separator = getSeparator();
+        // we don't hold a reference to the parent folder, as that would pin the instance in memory
+        // as long as any any leaf item in the hierarchy is still open.
         int lastLevel = fullname.lastIndexOf(separator);
         // no parent folder?  Get the root one from the Store.
         if (lastLevel == -1) {
@@ -227,33 +227,33 @@
      * @throws MessagingException if there was a problem accessing the store
      */
     public synchronized boolean exists() throws MessagingException {
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
         try {
-            return checkExistance(connection); 
+            return checkExistance(connection);
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
-    
+
     /**
-     * Internal routine for checking existance using an 
+     * Internal routine for checking existance using an
      * already obtained connection.  Used for situations
-     * where the list information needs updating but 
-     * we'd end up acquiring a new connection because 
-     * the folder isn't open yet. 
-     * 
+     * where the list information needs updating but
+     * we'd end up acquiring a new connection because
+     * the folder isn't open yet.
+     *
      * @param connection The connection to use.
-     * 
+     *
      * @return true if the folder exists, false for non-existence.
      * @exception MessagingException
      */
     private boolean checkExistance(IMAPConnection connection) throws MessagingException {
         // get the list response for this folder.
         List responses = connection.list("", fullname);
-        // NB, this grabs the latest information and updates 
-        // the type information also.  Note also that we need to  
-        // use the mailbox name, not the full name.  This is so 
-        // the namespace folders will return the correct response. 
+        // NB, this grabs the latest information and updates
+        // the type information also.  Note also that we need to
+        // use the mailbox name, not the full name.  This is so
+        // the namespace folders will return the correct response.
         listInfo = findListResponse(responses, getMailBoxName());
 
         if (listInfo == null) {
@@ -269,10 +269,10 @@
             folderType |= HOLDS_MESSAGES;
         }
 
-        // also update the separator information.  This will allow 
-        // use to skip a call later 
+        // also update the separator information.  This will allow
+        // use to skip a call later
         separator = listInfo.separator;
-        // this can be omitted in the response, so assume a default 
+        // this can be omitted in the response, so assume a default
         if (separator == '\0') {
             separator = '/';
         }
@@ -280,7 +280,7 @@
         // updated ok, so it must be there.
         return true;
     }
-    
+
 
 
     /**
@@ -290,9 +290,9 @@
      * <li>'*' which matches any character including hierarchy delimiters</li>
      * </ul>
      * This can be invoked when the folder is closed.
-     * 
+     *
      * @param pattern the pattern to search for
-     * 
+     *
      * @return a possibly empty array containing Folders that matched the pattern
      * @throws MessagingException
      *                if there was a problem accessing the store
@@ -309,9 +309,9 @@
      * If the store does not support the concept of subscription then this should match against
      * all folders; the default implementation of this method achieves this by defaulting to the
      * {@link #list(String)} method.
-     * 
+     *
      * @param pattern the pattern to search for
-     * 
+     *
      * @return a possibly empty array containing subscribed Folders that matched the pattern
      * @throws MessagingException
      *                if there was a problem accessing the store
@@ -330,26 +330,26 @@
      * @throws MessagingException if there was a problem accessing the store
      */
 	public synchronized char getSeparator() throws MessagingException {
-        // not determined yet, we need to ask the server for the information 
+        // not determined yet, we need to ask the server for the information
         if (separator == UNDETERMINED) {
-            IMAPConnection connection = getConnection(); 
+            IMAPConnection connection = getConnection();
             try {
                 List responses = connection.list("", fullname);
                 IMAPListResponse info = findListResponse(responses, fullname);
 
-                // if we didn't get any hits, then we just assume a reasonable default. 
+                // if we didn't get any hits, then we just assume a reasonable default.
                 if (info == null) {
                     separator = '/';
                 }
                 else {
                     separator = info.separator;
-                    // this can be omitted in the response, so assume a default 
+                    // this can be omitted in the response, so assume a default
                     if (separator == '\0') {
                         separator = '/';
                     }
                 }
             } finally {
-                releaseConnection(connection); 
+                releaseConnection(connection);
             }
         }
         return separator;
@@ -358,19 +358,19 @@
 
     /**
      * Return whether this folder can hold just messages or also
-     * subfolders.  
+     * subfolders.
      *
-     * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending 
-     * on the folder capabilities. 
+     * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending
+     * on the folder capabilities.
      * @exception MessagingException
      */
 	public int getType() throws MessagingException {
-        // checking the validity will update the type information 
-        // if it succeeds. 
+        // checking the validity will update the type information
+        // if it succeeds.
         checkFolderValidity();
 		return folderType;
 	}
-    
+
 
     /**
      * Create a new folder capable of containing subfolder and/or messages as
@@ -378,18 +378,18 @@
      * name will be recursively created.
      * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
      * is sent to all FolderListeners registered with this Folder or with the Store.
-     * 
+     *
      * @param newType the type, indicating if this folder should contain subfolders, messages or both
-     * 
+     *
      * @return true if the folder was sucessfully created
      * @throws MessagingException
      *                if there was a problem accessing the store
      */
 	public synchronized boolean create(int newType) throws MessagingException {
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
         try {
 
-            // by default, just create using the fullname.  
+            // by default, just create using the fullname.
             String newPath = fullname;
 
             // if this folder is expected to only hold additional folders, we need to
@@ -400,21 +400,21 @@
             try {
                 // go create this
                 connection.createMailbox(newPath);
-                // verify this exists...also updates some of the status 
-                boolean reallyCreated = checkExistance(connection); 
+                // verify this exists...also updates some of the status
+                boolean reallyCreated = checkExistance(connection);
                 // broadcast a creation event.
                 notifyFolderListeners(FolderEvent.CREATED);
-                return reallyCreated; 
+                return reallyCreated;
             } catch (MessagingException e) {
                 //TODO add folder level debug logging.
             }
             // we have a failure
             return false;
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
 	}
-    
+
 
     /**
      * Return the subscription status of this folder.
@@ -424,7 +424,7 @@
      */
     public synchronized boolean isSubscribed() {
         try {
-            IMAPConnection connection = getConnection(); 
+            IMAPConnection connection = getConnection();
             try {
                 // get the lsub response for this folder.
                 List responses = connection.listSubscribed("", fullname);
@@ -434,18 +434,18 @@
                     return false;
                 }
                 else {
-                    // a NOSELECT flag response indicates the mailbox is no longer 
-                    // selectable, so it's also no longer subscribed to. 
-                    return !response.noselect; 
+                    // a NOSELECT flag response indicates the mailbox is no longer
+                    // selectable, so it's also no longer subscribed to.
+                    return !response.noselect;
                 }
             } finally {
-                releaseConnection(connection); 
+                releaseConnection(connection);
             }
         } catch (MessagingException e) {
-            // Can't override to throw a MessagingException on this method, so 
-            // just swallow any exceptions and assume false is the answer. 
+            // Can't override to throw a MessagingException on this method, so
+            // just swallow any exceptions and assume false is the answer.
         }
-        return false; 
+        return false;
     }
 
 
@@ -456,8 +456,8 @@
      *            The new subscription state.
      */
     public synchronized void setSubscribed(boolean flag) throws MessagingException {
-        IMAPConnection connection = getConnection(); 
-        try {             
+        IMAPConnection connection = getConnection();
+        try {
             if (flag) {
                 connection.subscribe(fullname);
             }
@@ -465,7 +465,7 @@
                 connection.unsubscribe(fullname);
             }
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
 
@@ -480,7 +480,7 @@
 	public synchronized boolean hasNewMessages() throws MessagingException {
         // the folder must exist for this to work.
         checkFolderValidity();
-        
+
         // get the freshest status information.
         refreshStatus(true);
         // return the indicator from the message state.
@@ -498,17 +498,17 @@
      */
     public Folder getFolder(String name) throws MessagingException {
         // this must be a real, valid folder to hold a subfolder
-        checkFolderValidity(); 
+        checkFolderValidity();
         if (!holdsFolders()) {
-            throw new MessagingException("Folder " + fullname + " cannot hold subfolders"); 
+            throw new MessagingException("Folder " + fullname + " cannot hold subfolders");
         }
-        // our separator does not get determined until we ping the server for it.  We 
-        // might need to do that now, so we need to use the getSeparator() method to retrieve this. 
-        char separator = getSeparator(); 
-        
+        // our separator does not get determined until we ping the server for it.  We
+        // might need to do that now, so we need to use the getSeparator() method to retrieve this.
+        char separator = getSeparator();
+
         return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
     }
-    
+
 
     /**
      * Delete this folder and possibly any subfolders. This operation can only be
@@ -561,7 +561,7 @@
             }
         }
 
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
         try {
             // delete this one now.
             connection.deleteMailbox(fullname);
@@ -575,7 +575,7 @@
         } catch (MessagingException e) {
             // ignored
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
         return false;
 	}
@@ -596,12 +596,12 @@
         // but we must also exist
         checkFolderValidity();
 
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
         try {
             // delete this one now.
             connection.renameMailbox(fullname, f.getFullName());
-            // we renamed, so get a fresh set of status 
-            refreshStatus(false); 
+            // we renamed, so get a fresh set of status
+            refreshStatus(false);
 
             // notify interested parties about the deletion.
             notifyFolderRenamedListeners(f);
@@ -609,7 +609,7 @@
         } catch (MessagingException e) {
             // ignored
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
         return false;
 	}
@@ -635,12 +635,12 @@
             // can only be performed on a closed folder
             checkClosed();
             // ask the store to kindly hook us up with a connection.
-            // We're going to hang on to this until we're closed, so store it in 
-            // the Folder field.  We need to make sure our mailbox is selected while 
-            // we're working things. 
-            currentConnection = ((IMAPStore)store).getFolderConnection(this); 
-            // we need to make ourselves a handler of unsolicited responses 
-            currentConnection.addResponseHandler(this); 
+            // We're going to hang on to this until we're closed, so store it in
+            // the Folder field.  We need to make sure our mailbox is selected while
+            // we're working things.
+            currentConnection = ((IMAPStore)store).getFolderConnection(this);
+            // we need to make ourselves a handler of unsolicited responses
+            currentConnection.addResponseHandler(this);
             // record our open mode
             this.mode = mode;
 
@@ -656,9 +656,9 @@
                         throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
                     }
                 }
-                
-                // save this status and when we got it for later updating. 
-                cachedStatus = status; 
+
+                // save this status and when we got it for later updating.
+                cachedStatus = status;
                 // mark when we got this
                 lastStatusTimeStamp = System.currentTimeMillis();
 
@@ -672,49 +672,26 @@
                 availableFlags = status.availableFlags;
                 permanentFlags = status.permanentFlags;
 
-                // create a our caches
-                messages = new LinkedList(); 
+                // create a our caches.  These are empty initially
+                messageCache = new HashMap();
                 uidCache = new HashMap();
-                // this is a real pain, but because we need to track updates 
-                // to message sequence numbers while the folder is open, the 
-                // messages list needs to be populated with Message objects 
-                // to keep track of things.  The IMAPMessage objects will not 
-                // retrieve information from the server until required, so they're
-                // relatively lightweight at this point. 
-                populateMessageCache(); 
 
                 // we're open for business folks!
                 folderOpen = true;
                 notifyConnectionListeners(ConnectionEvent.OPENED);
             } finally {
-                // NB:  this doesn't really release this, but it does drive 
-                // the processing of any unsolicited responses. 
-                releaseConnection(currentConnection); 
+                // NB:  this doesn't really release this, but it does drive
+                // the processing of any unsolicited responses.
+                releaseConnection(currentConnection);
             }
         }
 	}
-    
-    
-    /**
-     * Populate the message cache with dummy messages, ensuring we're filled 
-     * up to the server-reported number of messages. 
-     * 
-     * @exception MessagingException
-     */
-    protected void populateMessageCache() {
-        // spin through the server-reported number of messages and add a dummy one to 
-        // the cache.  The message number is assigned from the id counter, the 
-        // sequence number is the cache position + 1. 
-        for (int i = messages.size(); i < maxSequenceNumber; i++) {
-            messages.add(new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, i+1));  
-        }
-    }
-    
+
 
     /**
      * Close this folder; it must already be open.
      * A  @link ConnectionEvent#CLOSED} event is sent to all listeners registered
-     {* 
+     {*
      * with this folder.
      *
      * @param expunge whether to expunge all deleted messages
@@ -723,56 +700,56 @@
 	public synchronized void close(boolean expunge) throws MessagingException {
 		// Can only be performed on an open folder
 		checkOpen();
-        cleanupFolder(expunge, false); 
+        cleanupFolder(expunge, false);
 	}
-    
-    
+
+
     /**
      * Do folder cleanup.  This is used both for normal
      * close operations, and adnormal closes where the
      * server has sent us a BYE message.
-     * 
+     *
      * @param expunge Indicates whether open messages should be expunged.
      * @param disconnected
      *                The disconnected flag.  If true, the server has cut
      *                us off, which means our connection can not be returned
      *                to the connection pool.
-     * 
+     *
      * @exception MessagingException
      */
     protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
 		folderOpen = false;
-        uidCache = null; 
-        messages = null; 
+        uidCache = null;
+        messageCache = null;
         // if we have a connection active at the moment
         if (currentConnection != null) {
             // was this a forced disconnect by the server?
             if (disconnected) {
-                currentConnection.setClosed(); 
+                currentConnection.setClosed();
             }
             else {
-                // The CLOSE operation depends on what mode was used to select the mailbox.  
-                // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE 
-                // is issued, any deleted messages will be expunged.  If we've been asked not 
-                // to expunge the messages, we have a problem.  The solution is to reselect the 
-                // mailbox using EXAMINE, which will not expunge messages when closed.  
+                // The CLOSE operation depends on what mode was used to select the mailbox.
+                // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE
+                // is issued, any deleted messages will be expunged.  If we've been asked not
+                // to expunge the messages, we have a problem.  The solution is to reselect the
+                // mailbox using EXAMINE, which will not expunge messages when closed.
                 if (mode == READ_WRITE && !expunge) {
-                    // we can ignore the result...we're just switching modes. 
+                    // we can ignore the result...we're just switching modes.
                     currentConnection.openMailbox(fullname, true);
                 }
-                
-                // have this close the selected mailbox 
-                currentConnection.closeMailbox(); 
-            }
-            currentConnection.removeResponseHandler(this); 
-            // we need to release the connection to the Store once we're closed 
-            ((IMAPStore)store).releaseFolderConnection(this, currentConnection); 
-            currentConnection = null; 
+
+                // have this close the selected mailbox
+                currentConnection.closeMailbox();
+            }
+            currentConnection.removeResponseHandler(this);
+            // we need to release the connection to the Store once we're closed
+            ((IMAPStore)store).releaseFolderConnection(this, currentConnection);
+            currentConnection = null;
         }
 		notifyConnectionListeners(ConnectionEvent.CLOSED);
     }
 
-    
+
     /**
      * Tests the open status of the folder.
      *
@@ -793,8 +770,8 @@
             return new Flags(permanentFlags);
         }
         else {
-            // a null return is expected if not there. 
-            return null; 
+            // a null return is expected if not there.
+            return null;
         }
 	}
 
@@ -823,21 +800,21 @@
      * The default implmentation of this method iterates over all messages
      * in the folder; subclasses should override if possible to provide a more
      * efficient implementation.
-     * 
-     * NB:  This is an override of the default Folder implementation, which 
-     * examines each of the messages in the folder.  IMAP has more efficient 
-     * mechanisms for grabbing the information. 
+     *
+     * NB:  This is an override of the default Folder implementation, which
+     * examines each of the messages in the folder.  IMAP has more efficient
+     * mechanisms for grabbing the information.
      *
      * @return the number of new messages, or -1 if unknown
      * @throws MessagingException if there was a problem accessing the store
      */
     public synchronized int getNewMessageCount() throws MessagingException {
-        // the folder must be a real one for this to work. 
-        checkFolderValidity(); 
-        // now get current status from the folder 
-        refreshStatus(false); 
-        // this should be current now. 
-        return recentMessages; 
+        // the folder must be a real one for this to work.
+        checkFolderValidity();
+        // now get current status from the folder
+        refreshStatus(false);
+        // this should be current now.
+        return recentMessages;
     }
 
 
@@ -849,10 +826,10 @@
      * The default implmentation of this method iterates over all messages
      * in the folder; subclasses should override if possible to provide a more
      * efficient implementation.
-     * 
-     * NB:  This is an override of the default Folder implementation, which 
-     * examines each of the messages in the folder.  IMAP has more efficient 
-     * mechanisms for grabbing the information. 
+     *
+     * NB:  This is an override of the default Folder implementation, which
+     * examines each of the messages in the folder.  IMAP has more efficient
+     * mechanisms for grabbing the information.
      *
      * @return the number of new messages, or -1 if unknown
      * @throws MessagingException if there was a problem accessing the store
@@ -870,9 +847,9 @@
 
             // UNSEEN is a false test on SEEN using the search criteria.
             SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
-            
+
             // ask the store to kindly hook us up with a connection.
-            IMAPConnection connection = getConnection(); 
+            IMAPConnection connection = getConnection();
             try {
                 // search using the connection directly rather than calling our search() method so we don't
                 // need to instantiate each of the matched messages.  We're really only interested in the count
@@ -881,7 +858,7 @@
                 // update the unseen count.
                 unseenMessages = matches == null ? 0 : matches.length;
             } finally {
-                releaseConnection(connection); 
+                releaseConnection(connection);
             }
         }
         // return our current message count.
@@ -917,9 +894,9 @@
 
             // UNSEEN is a false test on SEEN using the search criteria.
             SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);
-            
+
             // ask the store to kindly hook us up with a connection.
-            IMAPConnection connection = getConnection(); 
+            IMAPConnection connection = getConnection();
             try {
                 // search using the connection directly rather than calling our search() method so we don't
                 // need to instantiate each of the matched messages.  We're really only interested in the count
@@ -927,7 +904,7 @@
                 int[] matches = connection.searchMailbox(criteria);
                 return matches == null ? 0 : matches.length;
             } finally {
-                releaseConnection(connection); 
+                releaseConnection(connection);
             }
         }
 	}
@@ -939,9 +916,9 @@
      * Clients should note that the index for a specific message may change
      * if the folder is expunged; {@link Message} objects should be used as
      * references instead.
-     * 
+     *
      * @param msgNum The message sequence number of the target message.
-     * 
+     *
      * @return the message
      * @throws MessagingException
      *                if there was a problem accessing the store
@@ -952,19 +929,28 @@
         // Check the validity of the message number.  This may require pinging the server to
         // see if there are new messages in the folder.
         checkMessageValidity(msgNum);
-        // ok, if the message number is within range, we should have this in the 
-        // messages list.  Just return the element. 
-        return (Message)messages.get(msgNum - 1); 
+        // create the mapping key for this
+        Integer messageKey = new Integer(msgNum);
+        // ok, if the message number is within range, we should have this in the
+        // messages list.  Just return the element.
+        Message message = (Message)messageCache.get(messageKey);
+        // if not in the cache, create a dummy add it in.  The message body will be
+        // retrieved on demand
+        if (message == null) {
+            message = new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, msgNum);
+            messageCache.put(messageKey, message);
+        }
+        return message;
     }
 
 
     /**
      * Retrieve a range of messages for this folder.
      * messages indices start at 1 not zero.
-     * 
+     *
      * @param start  Index of the first message to fetch, inclusive.
      * @param end    Index of the last message to fetch, inclusive.
-     * 
+     *
      * @return An array of the fetched messages.
      * @throws MessagingException
      *                if there was a problem accessing the store
@@ -973,13 +959,13 @@
         // Can only be performed on an Open folder
         checkOpen();
         Message[] messageRange = new Message[end - start + 1];
-        
+
         for (int i = 0; i < messageRange.length; i++) {
-            // NB:  getMessage() requires values that are origin 1, so there's 
-            // no need to adjust the value by other than the start position. 
+            // NB:  getMessage() requires values that are origin 1, so there's
+            // no need to adjust the value by other than the start position.
             messageRange[i] = getMessage(start + i);
         }
-        return messageRange; 
+        return messageRange;
     }
 
 
@@ -988,18 +974,18 @@
      * to all listeners registered with this folder when all messages have been appended.
      * If the array contains a previously expunged message, it must be re-appended to the Store
      * and implementations must not abort this operation.
-     * 
+     *
      * @param msgs   The array of messages to append to the folder.
-     * 
+     *
      * @throws MessagingException
      *                if there was a problem accessing the store
      */
 	public synchronized void appendMessages(Message[] msgs) throws MessagingException {
-        checkFolderValidity(); 
+        checkFolderValidity();
         for (int i = 0; i < msgs.length; i++) {
-            Message msg = msgs[i]; 
-            
-            appendMessage(msg); 
+            Message msg = msgs[i];
+
+            appendMessage(msg);
         }
 	}
 
@@ -1014,52 +1000,52 @@
      * @see FetchProfile
      */
     public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {
-        
-        // we might already have the information being requested, so ask each of the 
-        // messages in the list to evaluate itself against the profile.  We'll only ask 
-        // the server to send information that's required. 
-        List fetchSet = new ArrayList(); 
-        
+
+        // we might already have the information being requested, so ask each of the
+        // messages in the list to evaluate itself against the profile.  We'll only ask
+        // the server to send information that's required.
+        List fetchSet = new ArrayList();
+
         for (int i = 0; i < messages.length; i++) {
-            Message msg = messages[i]; 
-            // the message is missing some of the information still.  Keep this in the list. 
-            // even if the message is only missing one piece of information, we still fetch everything. 
+            Message msg = messages[i];
+            // the message is missing some of the information still.  Keep this in the list.
+            // even if the message is only missing one piece of information, we still fetch everything.
             if (((IMAPMessage)msg).evaluateFetch(profile)) {
-                fetchSet.add(msg); 
+                fetchSet.add(msg);
             }
         }
-        
-        // we've got everything already, no sense bothering the server 
+
+        // we've got everything already, no sense bothering the server
         if (fetchSet.isEmpty()) {
             return;
         }
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
         try {
-            // ok, from this point onward, we don't want any threads messing with the 
-            // message cache.  A single processed EXPUNGE could make for a very bad day 
+            // ok, from this point onward, we don't want any threads messing with the
+            // message cache.  A single processed EXPUNGE could make for a very bad day
             synchronized(this) {
-                // get the message set for this 
-                String messageSet = generateMessageSet(fetchSet); 
-                // fetch all of the responses 
-                List responses = connection.fetch(messageSet, profile); 
-                
-                // IMPORTANT:  We must do our updates while synchronized to keep the 
-                // cache from getting updated underneath us.   This includes 
-                // not releasing the connection until we're done to delay processing any 
-                // pending expunge responses.  
+                // get the message set for this
+                String messageSet = generateMessageSet(fetchSet);
+                // fetch all of the responses
+                List responses = connection.fetch(messageSet, profile);
+
+                // IMPORTANT:  We must do our updates while synchronized to keep the
+                // cache from getting updated underneath us.   This includes
+                // not releasing the connection until we're done to delay processing any
+                // pending expunge responses.
                 for (int i = 0; i < responses.size(); i++) {
-                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
-                    Message msg = getMessage(response.getSequenceNumber()); 
-                    // Belt and Braces.  This should never be false. 
+                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
+                    Message msg = getMessage(response.getSequenceNumber());
+                    // Belt and Braces.  This should never be false.
                     if (msg != null) {
-                        // have the message apply this to itself. 
-                        ((IMAPMessage)msg).updateMessageInformation(response); 
+                        // have the message apply this to itself.
+                        ((IMAPMessage)msg).updateMessageInformation(response);
                     }
                 }
             }
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
         return;
     }
@@ -1069,18 +1055,18 @@
      * This method may be overridden by subclasses that can optimize the setting
      * of flags on multiple messages at once; the default implementation simply calls
      * {@link Message#setFlags(Flags, boolean)} for each supplied messages.
-     * 
+     *
      * @param messages whose flags should be set
      * @param flags    the set of flags to modify
      * @param set      Indicates whether the flags should be set or cleared.
-     * 
+     *
      * @throws MessagingException
      *                if there was a problem accessing the store
      */
     public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
-        // this is a list of messages for the change broadcast after the update 
-        List updatedMessages = new ArrayList(); 
-        
+        // this is a list of messages for the change broadcast after the update
+        List updatedMessages = new ArrayList();
+
         synchronized(this) {
             // the folder must be open and writeable.
             checkOpenReadWrite();
@@ -1098,61 +1084,61 @@
                 return;
             }
             // ask the store to kindly hook us up with a connection.
-            IMAPConnection connection = getConnection(); 
+            IMAPConnection connection = getConnection();
 
             try {
                 // and have the connection set this
                 List responses = connection.setFlags(messageSet, flags, set);
                 // retrieve each of the messages from our cache, and do the flag update.
-                // we need to keep the list so we can broadcast a change update event 
-                // when we're finished. 
+                // we need to keep the list so we can broadcast a change update event
+                // when we're finished.
                 for (int i = 0; i < responses.size(); i++) {
-                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
+                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
 
-                    // get the updated message and update the internal state. 
-                    Message message = getMessage(response.sequenceNumber); 
-                    // this shouldn't happen, but it might have been expunged too. 
+                    // get the updated message and update the internal state.
+                    Message message = getMessage(response.sequenceNumber);
+                    // this shouldn't happen, but it might have been expunged too.
                     if (message != null) {
-                        ((IMAPMessage)message).updateMessageInformation(response); 
-                        updatedMessages.add(message); 
-                    }     
+                        ((IMAPMessage)message).updateMessageInformation(response);
+                        updatedMessages.add(message);
+                    }
                 }
             } finally {
-                releaseConnection(connection); 
+                releaseConnection(connection);
             }
         }
-        
-        // ok, we're no longer holding the lock.  Now go broadcast the update for each 
-        // of the affected messages. 
+
+        // ok, we're no longer holding the lock.  Now go broadcast the update for each
+        // of the affected messages.
         for (int i = 0; i < updatedMessages.size(); i++) {
-            Message message = (Message)updatedMessages.get(i); 
+            Message message = (Message)updatedMessages.get(i);
             notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
         }
     }
 
-    
+
     /**
      * Set flags on a range of messages to the supplied value.
      * This method may be overridden by subclasses that can optimize the setting
      * of flags on multiple messages at once; the default implementation simply
      * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
-     * 
+     *
      * @param start  first message end set
      * @param end    last message end set
      * @param flags  the set of flags end modify
      * @param value  Indicates whether the flags should be set or cleared.
-     * 
+     *
      * @throws MessagingException
      *                if there was a problem accessing the store
      */
     public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
-        Message[] msgs = new Message[end - start + 1]; 
-        
+        Message[] msgs = new Message[end - start + 1];
+
         for (int i = start; i <= end; i++) {
             msgs[i] = getMessage(i);
         }
-        // go do a bulk set operation on these messages 
-        setFlags(msgs, flags, value); 
+        // go do a bulk set operation on these messages
+        setFlags(msgs, flags, value);
     }
 
     /**
@@ -1160,25 +1146,25 @@
      * This method may be overridden by subclasses that can optimize the setting
      * of flags on multiple messages at once; the default implementation simply
      * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
-     * 
+     *
      * @param ids    the indexes of the messages to set
      * @param flags  the set of flags end modify
      * @param value  Indicates whether the flags should be set or cleared.
-     * 
+     *
      * @throws MessagingException
      *                if there was a problem accessing the store
      */
     public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
-        Message[] msgs = new Message[ids.length]; 
-        
+        Message[] msgs = new Message[ids.length];
+
         for (int i = 0; i <ids.length; i++) {
             msgs[i] = getMessage(ids[i]);
         }
-        // go do a bulk set operation on these messages 
-        setFlags(msgs, flags, value); 
+        // go do a bulk set operation on these messages
+        setFlags(msgs, flags, value);
     }
 
-    
+
     /**
      * Copy the specified messages to another folder.
      * The default implementation simply appends the supplied messages to the
@@ -1188,14 +1174,14 @@
      * @throws MessagingException if there was a problem accessing the store
      */
     public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException {
-        // the default implementation just appends the messages to the target.  If 
-        // we're copying between two folders of the same store, we can get the server to 
-        // do most of the work for us without needing to fetch all of the message data.  
-        // If we're dealing with two different Store instances, we need to do this the 
+        // the default implementation just appends the messages to the target.  If
+        // we're copying between two folders of the same store, we can get the server to
+        // do most of the work for us without needing to fetch all of the message data.
+        // If we're dealing with two different Store instances, we need to do this the
         // hardway.
         if (getStore() != folder.getStore()) {
-            super.copyMessages(messages, folder); 
-            return; 
+            super.copyMessages(messages, folder);
+            return;
         }
 
         // turn this into a set of message numbers
@@ -1205,17 +1191,17 @@
             return;
         }
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
 
         try {
-            // ask the server to copy this information over to the other mailbox. 
-            connection.copyMessages(messageSet, folder.getFullName()); 
+            // ask the server to copy this information over to the other mailbox.
+            connection.copyMessages(messageSet, folder.getFullName());
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
 
-    
+
 
     /**
      * Permanently delete all supplied messages that have the DELETED flag set from the Store.
@@ -1233,7 +1219,7 @@
         checkReadWrite();
 
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
         List expunges = null;
 
         try {
@@ -1243,22 +1229,22 @@
             // expunge messages that also marked messages as expunged.
             expunges = connection.expungeMailbox();
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
 
-        // we get one EXPUNGE message for each message that's expunged.  They MUST be processed in 
-        // order, as the message sequence numbers represent a relative position that takes into account 
-        // previous expunge operations.  For example, if message sequence numbers 5, 6, and 7 are 
-        // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged. 
-        Message[] messages = new Message[expunges.size()]; 
+        // we get one EXPUNGE message for each message that's expunged.  They MUST be processed in
+        // order, as the message sequence numbers represent a relative position that takes into account
+        // previous expunge operations.  For example, if message sequence numbers 5, 6, and 7 are
+        // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged.
+        Message[] messages = new Message[expunges.size()];
 
         // now we need to protect the internal structures
         synchronized (this) {
-            // expunge all of the messages from the message cache.  This keeps the sequence 
-            // numbers up to-date. 
+            // expunge all of the messages from the message cache.  This keeps the sequence
+            // numbers up to-date.
             for (int i = 0; i < expunges.size(); i++) {
-                IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i); 
-                messages[i] = expungeMessage(response.getSize()); 
+                IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i);
+                messages[i] = expungeMessage(response.getSize());
             }
         }
         // if we have messages that have been removed, broadcast the notification.
@@ -1289,14 +1275,14 @@
         checkOpen();
 
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
 
         try {
             // just search everything
             int[] messageNumbers = connection.searchMailbox(term);
             return resolveMessages(messageNumbers);
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
 
@@ -1328,7 +1314,7 @@
         }
 
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
 
         try {
 
@@ -1336,31 +1322,31 @@
             int[] messageNumbers = connection.searchMailbox(messageSet, term);
             return resolveMessages(messageNumbers);
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
 
     /**
      * Get the UID validity value for this Folder.
-     * 
-     * @return The current UID validity value, as a long. 
+     *
+     * @return The current UID validity value, as a long.
      * @exception MessagingException
      */
     public synchronized long getUIDValidity() throws MessagingException
     {
-        // get the latest status to make sure we have the  
-        // most current. 
-        refreshStatus(true); 
-        return uidValidity; 
+        // get the latest status to make sure we have the
+        // most current.
+        refreshStatus(true);
+        return uidValidity;
     }
 
     /**
-     * Retrieve a message using the UID rather than the 
+     * Retrieve a message using the UID rather than the
      * message sequence number.  Returns null if the message
      * doesn't exist.
-     * 
+     *
      * @param uid    The target UID.
-     * 
+     *
      * @return the Message object.  Returns null if the message does
      *         not exist.
      * @exception MessagingException
@@ -1369,46 +1355,46 @@
     {
         // only allowed on open folders
         checkOpen();
-        
-        Long key = new Long(uid); 
-        // first check to see if we have a cached value for this 
-        synchronized(messages) {
-            Message msg = (Message)uidCache.get(key); 
+
+        Long key = new Long(uid);
+        // first check to see if we have a cached value for this
+        synchronized(messageCache) {
+            Message msg = (Message)uidCache.get(key);
             if (msg != null) {
-                return msg; 
+                return msg;
             }
         }
 
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
 
         try {
-            // locate the message identifier 
+            // locate the message identifier
             IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
-            // if nothing is returned, the message doesn't exist 
+            // if nothing is returned, the message doesn't exist
             if (imapuid == null) {
-                return null; 
+                return null;
             }
-            
-            
+
+
             // retrieve the actual message object and place this in the UID cache
-            return retrieveMessageByUid(key, imapuid.messageNumber); 
+            return retrieveMessageByUid(key, imapuid.messageNumber);
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
 
     /**
-     * Get a series of messages using a UID range.  The 
-     * special value LASTUID can be used to mark the 
+     * Get a series of messages using a UID range.  The
+     * special value LASTUID can be used to mark the
      * last available message.
-     * 
+     *
      * @param start  The start of the UID range.
      * @param end    The end of the UID range.  The special value
      *               LASTUID can be used to request all messages up
      *               to the last UID.
-     * 
-     * @return An array containing all of the messages in the 
+     *
+     * @return An array containing all of the messages in the
      *         range.
      * @exception MessagingException
      */
@@ -1417,34 +1403,34 @@
         // only allowed on open folders
         checkOpen();
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
 
         try {
-            // locate the message identifier 
+            // locate the message identifier
             List uids = connection.getSequenceNumbersForUids(start, end);
             Message[] msgs = new Message[uids.size()];
-            
-            // fill in each of the messages based on the returned value 
+
+            // fill in each of the messages based on the returned value
             for (int i = 0; i < msgs.length; i++) {
-                IMAPUid uid = (IMAPUid)uids.get(i); 
+                IMAPUid uid = (IMAPUid)uids.get(i);
                 msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
             }
-            
-            return msgs; 
+
+            return msgs;
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
-        
-        
+
+
     }
 
     /**
-     * Retrieve a set of messages by explicit UIDs.  If 
-     * any message in the list does not exist, null 
+     * Retrieve a set of messages by explicit UIDs.  If
+     * any message in the list does not exist, null
      * will be returned for the corresponding item.
-     * 
+     *
      * @param ids    An array of UID values to be retrieved.
-     * 
+     *
      * @return An array of Message items the same size as the ids
      *         argument array.  This array will contain null
      *         entries for any UIDs that do not exist.
@@ -1454,134 +1440,134 @@
     {
         // only allowed on open folders
         checkOpen();
-        
+
         Message[] msgs = new Message[ids.length];
-        
+
         for (int i = 0; i < msgs.length; i++) {
-            msgs[i] = getMessageByUID(ids[i]); 
+            msgs[i] = getMessageByUID(ids[i]);
         }
-        
-        return msgs; 
+
+        return msgs;
     }
 
     /**
      * Retrieve the UID for a message from this Folder.
      * The argument Message MUST belong to this Folder
-     * instance, otherwise a NoSuchElementException will 
+     * instance, otherwise a NoSuchElementException will
      * be thrown.
-     * 
+     *
      * @param message The target message.
-     * 
+     *
      * @return The UID associated with this message.
      * @exception MessagingException
      */
     public synchronized long getUID(Message message) throws MessagingException
     {
-        // verify this actually is in this folder. 
-        checkMessageFolder(message); 
-        IMAPMessage msg = (IMAPMessage)message; 
-        
-        // we might already know this bit of information 
+        // verify this actually is in this folder.
+        checkMessageFolder(message);
+        IMAPMessage msg = (IMAPMessage)message;
+
+        // we might already know this bit of information
         if (msg.getUID() != -1) {
-            return msg.getUID(); 
+            return msg.getUID();
         }
 
         // ask the store to kindly hook us up with a connection.
-        IMAPConnection connection = getConnection(); 
+        IMAPConnection connection = getConnection();
 
         try {
-            // locate the message identifier 
+            // locate the message identifier
             IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
-            // if nothing is returned, the message doesn't exist 
+            // if nothing is returned, the message doesn't exist
             if (imapuid == null) {
                 return -1;
             }
-            // cache this information now that we've gotten it. 
+            // cache this information now that we've gotten it.
             addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
-            // return the UID information. 
-            return imapuid.uid; 
+            // return the UID information.
+            return imapuid.uid;
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
-    
+
     /**
      * Retrieve a message from a UID/message mapping.
-     * 
+     *
      * @param key       The UID key used for the mapping.
      * @param msgNumber The message sequence number.
-     * 
+     *
      * @return The Message object corresponding to the message.
      * @exception MessagingException
      */
     protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
     {
-        synchronized (messages) {
-            // first check the cache...this might have already been added. 
-            Message msg = (Message)uidCache.get(key); 
+        synchronized (messageCache) {
+            // first check the cache...this might have already been added.
+            Message msg = (Message)uidCache.get(key);
             if (msg != null) {
-                return msg; 
+                return msg;
             }
-            
-            // retrieve the message by sequence number 
-            msg = getMessage(msgNumber); 
-            // add this to our UID mapping cache. 
-            addToUidCache(key, msg); 
-            return msg; 
+
+            // retrieve the message by sequence number
+            msg = getMessage(msgNumber);
+            // add this to our UID mapping cache.
+            addToUidCache(key, msg);
+            return msg;
         }
     }
-    
-    
+
+
     /**
-     * Add a message to the UID mapping cache, ensuring that 
+     * Add a message to the UID mapping cache, ensuring that
      * the UID value is updated.
-     * 
+     *
      * @param key    The UID key.
      * @param msg    The message to add.
      */
-    protected void addToUidCache(Long key, Message msg) {  
-        synchronized (messages) {
-            ((IMAPMessage)msg).setUID(key.longValue());  
-            uidCache.put(key, msg); 
+    protected void addToUidCache(Long key, Message msg) {
+        synchronized (messageCache) {
+            ((IMAPMessage)msg).setUID(key.longValue());
+            uidCache.put(key, msg);
         }
     }
-    
-    
+
+
     /**
      * Append a single message to the IMAP Folder.
-     * 
+     *
      * @param msg    The message to append.
-     * 
+     *
      * @exception MessagingException
      */
-    protected synchronized void appendMessage(Message msg) throws MessagingException 
+    protected synchronized void appendMessage(Message msg) throws MessagingException
     {
-        // sort out the dates.  If no received date, use the sent date. 
-        Date date = msg.getReceivedDate(); 
+        // sort out the dates.  If no received date, use the sent date.
+        Date date = msg.getReceivedDate();
         if (date == null) {
-            date = msg.getSentDate(); 
+            date = msg.getSentDate();
         }
-        
-        Flags flags = msg.getFlags(); 
-        
-        // convert the message into an array of bytes we can attach as a literal. 
-        ByteArrayOutputStream out = new ByteArrayOutputStream(); 
-        
+
+        Flags flags = msg.getFlags();
+
+        // convert the message into an array of bytes we can attach as a literal.
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
         try {
-            msg.writeTo(out); 
+            msg.writeTo(out);
         } catch (IOException e) {
         }
-        
-        // now issue the append command 
-        IMAPConnection connection = getConnection(); 
+
+        // now issue the append command
+        IMAPConnection connection = getConnection();
         try {
-            connection.appendMessage(getFullName(), date, flags, out.toByteArray()); 
+            connection.appendMessage(getFullName(), date, flags, out.toByteArray());
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
     }
 
-    
+
     /**
      * Retrieve the list of matching groups from the IMAP server using the LIST
      * or LSUB command. The server does the wildcard matching for us.
@@ -1592,12 +1578,12 @@
      * @return An array of folders for the matching groups.
      */
     protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
-        IMAPConnection connection = getConnection(); 
-        // this is used to filter out our own folder from the search 
+        IMAPConnection connection = getConnection();
+        // this is used to filter out our own folder from the search
         String root = fullname + getSeparator();
-        
+
         List responses = null;
-        try {             
+        try {
 
 
             if (subscribed) {
@@ -1609,7 +1595,7 @@
                 responses = connection.list(root, pattern);
             }
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
 
         List folders = new ArrayList();
@@ -1660,8 +1646,8 @@
         if (messageNumber <= maxSequenceNumber) {
             return;
         }
-        
-        IMAPConnection connection = getConnection(); 
+
+        IMAPConnection connection = getConnection();
 
         synchronized (this) {
             try {
@@ -1669,7 +1655,7 @@
                 // by the response handlers.
                 connection.updateMailboxStatus();
             } finally {
-                releaseConnection(connection); 
+                releaseConnection(connection);
             }
         }
 
@@ -1688,7 +1674,7 @@
     /**
      * Ensure the folder is open.  Throws a MessagingException
      * if not in the correct state for the operation.
-     * 
+     *
      * @exception IllegalStateException
      */
     protected void checkOpen() throws IllegalStateException {
@@ -1700,7 +1686,7 @@
     /**
      * Ensure the folder is not open for operations
      * that require the folder to be closed.
-     * 
+     *
      * @exception IllegalStateException
      */
     protected void checkClosed() throws IllegalStateException {
@@ -1735,19 +1721,19 @@
 
 
     /**
-     * Notify the message changed listeners that a 
+     * Notify the message changed listeners that a
      * message contained in the folder has been updated.
-     * 
+     *
      * @param type   The type of update made to the message.
      * @param m      The message that was updated.
-     * 
+     *
      * @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message)
      */
     public void notifyMessageChangedListeners(int type, Message m) {
     	super.notifyMessageChangedListeners(type, m);
     }
 
-    
+
     /**
      * Retrieve the connection attached to this folder.  Throws an
      * exception if we don't have an active connection.
@@ -1756,115 +1742,115 @@
      * @exception MessagingException
      */
     protected synchronized IMAPConnection getConnection() throws MessagingException {
-        // don't have an open connection yet?  Just request a pool connection. 
+        // don't have an open connection yet?  Just request a pool connection.
         if (currentConnection == null) {
-            // request a connection from the central store. 
-            IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);  
-            // we need to make ourselves a handler of unsolicited responses 
-            connection.addResponseHandler(this); 
+            // request a connection from the central store.
+            IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);
+            // we need to make ourselves a handler of unsolicited responses
+            connection.addResponseHandler(this);
             return connection;
         }
-        // we have a connection for our use.  Just return it. 
-        return currentConnection; 
+        // we have a connection for our use.  Just return it.
+        return currentConnection;
     }
-    
-    
+
+
     /**
      * Release our connection back to the Store.
-     * 
+     *
      * @param connection The connection to release.
-     * 
+     *
      * @exception MessagingException
      */
     protected void releaseConnection(IMAPConnection connection) throws MessagingException {
-        // This is a bit of a pain.  We need to delay processing of the 
-        // unsolicited responses until after each user of the connection has 
-        // finished processing the expected responses.  We need to do this because 
-        // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED 
-        // messages will alter the message sequence numbers for the messages in the 
-        // cache.  Processing the EXPUNGED messages too early will result in 
-        // updates getting applied to the wrong message instances.  So, as a result, 
-        // we delay that stage of the processing until all expected responses have 
-        // been handled.  
-        
-        // process any pending messages before returning. 
-        connection.processPendingResponses(); 
-        // if no cached connection or this is somehow different from the cached one, just 
-        // return it. 
+        // This is a bit of a pain.  We need to delay processing of the
+        // unsolicited responses until after each user of the connection has
+        // finished processing the expected responses.  We need to do this because
+        // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED
+        // messages will alter the message sequence numbers for the messages in the
+        // cache.  Processing the EXPUNGED messages too early will result in
+        // updates getting applied to the wrong message instances.  So, as a result,
+        // we delay that stage of the processing until all expected responses have
+        // been handled.
+
+        // process any pending messages before returning.
+        connection.processPendingResponses();
+        // if no cached connection or this is somehow different from the cached one, just
+        // return it.
         if (currentConnection == null || connection != currentConnection) {
-            connection.removeResponseHandler(this); 
-            ((IMAPStore)store).releaseFolderConnection(this, connection);  
+            connection.removeResponseHandler(this);
+            ((IMAPStore)store).releaseFolderConnection(this, connection);
         }
-        // if we're open, then we don't have to worry about returning this connection 
-        // to the Store.  This is set up perfectly for our use right now. 
+        // if we're open, then we don't have to worry about returning this connection
+        // to the Store.  This is set up perfectly for our use right now.
     }
-    
-    
+
+
     /**
-     * Obtain a connection object for a Message attached to this Folder.  This 
-     * will be the Folder's connection, which is only available if the Folder 
+     * Obtain a connection object for a Message attached to this Folder.  This
+     * will be the Folder's connection, which is only available if the Folder
      * is currently open.
-     * 
-     * @return The connection object for the Message instance to use. 
+     *
+     * @return The connection object for the Message instance to use.
      * @exception MessagingException
      */
     synchronized IMAPConnection getMessageConnection() throws MessagingException {
         // if we're not open, the messages can't communicate either
         if (currentConnection == null) {
-            throw new FolderClosedException(this, "No Folder connections available"); 
+            throw new FolderClosedException(this, "No Folder connections available");
         }
-        // return the current Folder connection.  At this point, we'll be sharing the 
-        // connection between the Folder and the Message (and potentially, other messages).  The 
-        // command operations on the connection are synchronized so only a single command can be 
-        // issued at one time. 
-        return currentConnection; 
+        // return the current Folder connection.  At this point, we'll be sharing the
+        // connection between the Folder and the Message (and potentially, other messages).  The
+        // command operations on the connection are synchronized so only a single command can be
+        // issued at one time.
+        return currentConnection;
     }
-    
-    
+
+
     /**
-     * Release the connection object back to the Folder instance.  
-     * 
+     * Release the connection object back to the Folder instance.
+     *
      * @param connection The connection being released.
-     * 
+     *
      * @exception MessagingException
      */
     void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
-        // release it back to ourselves...this will drive unsolicited message processing. 
-        releaseConnection(connection); 
+        // release it back to ourselves...this will drive unsolicited message processing.
+        releaseConnection(connection);
     }
 
 
     /**
      * Refresh the status information on this folder.
-     * 
+     *
      * @param force  Force a status refresh always.
-     * 
+     *
      * @exception MessagingException
      */
     protected void refreshStatus(boolean force) throws MessagingException {
         // first check that any cached status we've received has gotten a little moldy.
         if (cachedStatus != null) {
-            // if not forcing, check the time out. 
+            // if not forcing, check the time out.
             if (!force) {
                 if (statusCacheTimeout > 0) {
                     long age = System.currentTimeMillis() - lastStatusTimeStamp;
                     if (age < statusCacheTimeout) {
-                        return;                 
+                        return;
                     }
                 }
             }
             // make sure the stale information is cleared out.
             cachedStatus = null;
         }
-        
-        IMAPConnection connection = getConnection(); 
+
+        IMAPConnection connection = getConnection();
         try {
             // ping the server for the list information for this folder
             cachedStatus = connection.getMailboxStatus(fullname);
             // mark when we got this
             lastStatusTimeStamp = System.currentTimeMillis();
         } finally {
-            releaseConnection(connection); 
+            releaseConnection(connection);
         }
 
         // refresh the internal state from the message information
@@ -1878,42 +1864,64 @@
     /**
      * Process an EXPUNGE response for a message, removing the
      * message from the message cache.
-     * 
+     *
      * @param sequenceNumber
      *               The sequence number for the expunged message.
-     * 
+     *
      * @return The Message object corresponding to this expunged
      *         message.
      * @exception MessagingException
      */
     protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {
-        // we need to remove this from our message list.  Deletion of a message by 
-        // sequence number shifts the sequence numbers of every message after 
-        // the target.  So we, need to remove the message, update its status, then 
-        // update the sequence numbers of every message after that.  This is a serious 
-        // pain!
-        
-        Message expungedMessage = (Message)messages.remove(sequenceNumber - 1); 
-        // mark the message as expunged. 
-        ((IMAPMessage)expungedMessage).setExpunged(true); 
-        
-        // update the message sequence numbers for every message after the 
-        // expunged one.  NB.  The sequence number is the cache index + 1
-        for (int i = sequenceNumber - 1; i < messages.size(); i++) {
-            IMAPMessage message = (IMAPMessage)messages.get(i); 
-            message.setSequenceNumber(i + 1); 
-        }
-        
+
+        // first process the expunged message.  We need to return a Message instance, so
+        // force this to be added to the cache
+        IMAPMessage expungedMessage = (IMAPMessage)getMessage(sequenceNumber);
+        // mark the message as expunged.
+        expungedMessage.setExpunged(true);
         // have we retrieved a UID for this message?  If we have, then it's in the UID cache and
         // needs removal from there also
         long uid = ((IMAPMessage)expungedMessage).getUID();
         if (uid >= 0) {
             uidCache.remove(new Long(uid));
         }
+        // because we need to jigger the keys of some of these, we had better have a working
+        // copy.
+        Map newCache = new HashMap();
+
+        // now process each message in the cache, making adjustments as necessary
+        Iterator i = messageCache.keySet().iterator();
+
+        while (i.hasNext()) {
+            Integer key = (Integer)i.next();
+            int index = key.intValue();
+            // if before the expunged message, just copy over to the
+            // new cache
+            if (index < sequenceNumber) {
+                newCache.put(key, messageCache.get(key));
+            }
+            // after the expunged message...we need to adjust this
+            else if (index > sequenceNumber) {
+                // retrieve the message using the current position,
+                // adjust the message sequence number, and add to the new
+                // message cache under the new key value
+                IMAPMessage message = (IMAPMessage)messageCache.get(key);
+                message.setSequenceNumber(index - 1);
+                newCache.put(new Integer(index - 1), message);
+            }
+            else {
+                // the expunged message.  We don't move this over to the new
+                // cache, and we've already done all processing of that message that's
+                // required
+            }
+        }
+
+        // replace the old cache now that everything has been adjusted
+        messageCache = newCache;
 
         // adjust the message count downward
         maxSequenceNumber--;
-        return expungedMessage; 
+        return expungedMessage;
     }
 
 
@@ -1945,19 +1953,19 @@
 
         return messages;
     }
-    
+
     /**
-     * Generate a message set string from a List of messages rather than an 
+     * Generate a message set string from a List of messages rather than an
      * array.
-     * 
+     *
      * @param messages The List of messages.
-     * 
-     * @return The evaluated message set string. 
+     *
+     * @return The evaluated message set string.
      * @exception MessagingException
      */
     protected String generateMessageSet(List messages) throws MessagingException {
-        Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]); 
-        return generateMessageSet(msgs); 
+        Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]);
+        return generateMessageSet(msgs);
     }
 
 
@@ -1996,8 +2004,8 @@
                     set.append(',');
                 }
 
-                // append the first number.  NOTE:  We append this directly rather than 
-                // use appendInteger(), which appends it using atom rules. 
+                // append the first number.  NOTE:  We append this directly rather than
+                // use appendInteger(), which appends it using atom rules.
                 set.append(Integer.toString(start.getSequenceNumber()));
 
                 // ok, we have a live one.  Now scan the list from here looking for the end of
@@ -2041,37 +2049,37 @@
         }
         return set.toString();
     }
-    
+
     /**
-     * Verify that this folder exists on the server before 
-     * performning an operation that requires a valid 
-     * Folder instance. 
-     * 
+     * Verify that this folder exists on the server before
+     * performning an operation that requires a valid
+     * Folder instance.
+     *
      * @exception MessagingException
      */
     protected void checkFolderValidity() throws MessagingException {
-        // if we are holding a current listinfo response, then 
-        // we have chached existance information.  In that case, 
-        // all of our status is presumed up-to-date and we can go 
-        // with that.  If we don't have the information, then we 
-        // ping the server for it. 
+        // if we are holding a current listinfo response, then
+        // we have chached existance information.  In that case,
+        // all of our status is presumed up-to-date and we can go
+        // with that.  If we don't have the information, then we
+        // ping the server for it.
         if (listInfo == null && !exists()) {
-            throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server"); 
+            throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server");
         }
     }
-    
-    
+
+
     /**
-     * Check if a Message is properly within the target 
-     * folder. 
-     * 
+     * Check if a Message is properly within the target
+     * folder.
+     *
      * @param msg    The message we're checking.
-     * 
+     *
      * @exception MessagingException
      */
     protected void checkMessageFolder(Message msg) throws MessagingException {
         if (msg.getFolder() != this) {
-            throw new NoSuchElementException("Message is not within the target Folder"); 
+            throw new NoSuchElementException("Message is not within the target Folder");
         }
     }
 
@@ -2094,293 +2102,292 @@
         }
         return null;
     }
-    
-    
+
+
     /**
-     * Protected class intended for subclass overrides.  For normal folders, 
+     * Protected class intended for subclass overrides.  For normal folders,
      * the mailbox name is fullname.  For Namespace root folders, the mailbox
-     * name is the prefix + separator.  
-     * 
-     * @return The string name to use as the mailbox name for exists() and issubscribed() 
+     * name is the prefix + separator.
+     *
+     * @return The string name to use as the mailbox name for exists() and issubscribed()
      *         calls.
      */
     protected String getMailBoxName() {
-        return fullname; 
+        return fullname;
     }
-    
+
     /**
-     * Handle an unsolicited response from the server.  Most unsolicited responses 
-     * are replies to specific commands sent to the server.  The remainder must 
-     * be handled by the Store or the Folder using the connection.  These are 
-     * critical to handle, as events such as expunged messages will alter the 
+     * Handle an unsolicited response from the server.  Most unsolicited responses
+     * are replies to specific commands sent to the server.  The remainder must
+     * be handled by the Store or the Folder using the connection.  These are
+     * critical to handle, as events such as expunged messages will alter the
      * sequence numbers of the live messages.  We need to keep things in sync.
-     * 
+     *
      * @param response The UntaggedResponse to process.
-     * 
+     *
      * @return true if we handled this response and no further handling is required.  false
      *         means this one wasn't one of ours.
      */
     public boolean handleResponse(IMAPUntaggedResponse response) {
-        // "you've got mail".  The message count has been updated.  There 
-        // are two posibilities.  Either there really are new messages, or 
-        // this is an update following an expunge.  If there are new messages, 
-        // we need to update the message cache and broadcast the change to 
+        // "you've got mail".  The message count has been updated.  There
+        // are two posibilities.  Either there really are new messages, or
+        // this is an update following an expunge.  If there are new messages,
+        // we need to update the message cache and broadcast the change to
         // any listeners.
         if (response.isKeyword("EXISTS")) {
-            // we need to update our cache, and also retrieve the new messages and 
-            // send them out in a broadcast update. 
-            int oldCount = maxSequenceNumber; 
-            maxSequenceNumber = ((IMAPSizeResponse)response).getSize(); 
-            populateMessageCache();  
-            // has the size grown?  We have to send the "you've got mail" announcement. 
+            // we need to update our cache, and also retrieve the new messages and
+            // send them out in a broadcast update.
+            int oldCount = maxSequenceNumber;
+            maxSequenceNumber = ((IMAPSizeResponse)response).getSize();
+            // has the size grown?  We have to send the "you've got mail" announcement.
             if (oldCount < maxSequenceNumber) {
                 try {
-                    Message[] messages = getMessages(oldCount + 1, maxSequenceNumber); 
-                    notifyMessageAddedListeners(messages); 
+                    Message[] messages = getMessages(oldCount + 1, maxSequenceNumber);
+                    notifyMessageAddedListeners(messages);
                 } catch (MessagingException e) {
-                    // should never happen in this context 
+                    // should never happen in this context
                 }
             }
-            return true; 
+            return true;
         }
-        // "you had mail".  A message was expunged from the server.  This MUST 
-        // be processed immediately, as any subsequent expunge messages will 
-        // shift the message numbers as a result of previous messages getting 
-        // removed.  We need to keep our internal cache in sync with the server. 
+        // "you had mail".  A message was expunged from the server.  This MUST
+        // be processed immediately, as any subsequent expunge messages will
+        // shift the message numbers as a result of previous messages getting
+        // removed.  We need to keep our internal cache in sync with the server.
         else if (response.isKeyword("EXPUNGE")) {
-            int messageNumber = ((IMAPSizeResponse)response).getSize(); 
+            int messageNumber = ((IMAPSizeResponse)response).getSize();
             try {
-                Message message = expungeMessage(messageNumber); 
+                Message message = expungeMessage(messageNumber);
 
-                // broadcast the message update. 
-                notifyMessageRemovedListeners(false, new Message[] {message}); 
+                // broadcast the message update.
+                notifyMessageRemovedListeners(false, new Message[] {message});
             } catch (MessagingException e) {
             }
-            // we handled this one. 
-            return true; 
+            // we handled this one.
+            return true;
         }
-        // just an update of recently arrived stuff?  Just update the field. 
+        // just an update of recently arrived stuff?  Just update the field.
         else if (response.isKeyword("RECENT")) {
-            recentMessages = ((IMAPSizeResponse)response).getSize();  
-            return true; 
+            recentMessages = ((IMAPSizeResponse)response).getSize();
+            return true;
         }
-        // The spec is not particularly clear what types of unsolicited 
-        // FETCH response can be sent.  The only one that is specifically 
-        // spelled out is flag updates.  If this is one of those, then 
-        // handle it. 
+        // The spec is not particularly clear what types of unsolicited
+        // FETCH response can be sent.  The only one that is specifically
+        // spelled out is flag updates.  If this is one of those, then
+        // handle it.
         else if (response.isKeyword("FETCH")) {
-            IMAPFetchResponse fetch = (IMAPFetchResponse)response;            
-            IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS); 
-            // if this is a flags response, get the message and update 
+            IMAPFetchResponse fetch = (IMAPFetchResponse)response;
+            IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS);
+            // if this is a flags response, get the message and update
             if (flags != null) {
                 try {
-                    // get the updated message and update the internal state. 
-                    IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber); 
-                    // this shouldn't happen, but it might have been expunged too. 
+                    // get the updated message and update the internal state.
+                    IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber);
+                    // this shouldn't happen, but it might have been expunged too.
                     if (message != null) {
-                        message.updateMessageInformation(fetch); 
+                        message.updateMessageInformation(fetch);
                     }
                     notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
                 } catch (MessagingException e) {
                 }
-                return true; 
+                return true;
             }
         }
-        // this is a BYE response on our connection.  This forces us to close, but 
-        // when we return the connection, the pool needs to get rid of it. 
+        // this is a BYE response on our connection.  This forces us to close, but
+        // when we return the connection, the pool needs to get rid of it.
         else if (response.isKeyword("BYE")) {
-            // this is essentially a close event.  We need to clean everything up 
-            // and make sure our connection is not returned to the general pool. 
+            // this is essentially a close event.  We need to clean everything up
+            // and make sure our connection is not returned to the general pool.
             try {
-                cleanupFolder(false, true); 
+                cleanupFolder(false, true);
             } catch (MessagingException e) {
             }
-            return true; 
+            return true;
         }
-        
-        // not a response the folder knows how to deal with. 
-        return false; 
-    }
-    

[... 260 lines stripped ...]


Mime
View raw message