Return-Path: Delivered-To: apmail-geronimo-scm-archive@www.apache.org Received: (qmail 34328 invoked from network); 3 Apr 2009 12:08:17 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 3 Apr 2009 12:08:17 -0000 Received: (qmail 12686 invoked by uid 500); 3 Apr 2009 12:08:17 -0000 Delivered-To: apmail-geronimo-scm-archive@geronimo.apache.org Received: (qmail 12609 invoked by uid 500); 3 Apr 2009 12:08:17 -0000 Mailing-List: contact scm-help@geronimo.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: dev@geronimo.apache.org List-Id: Delivered-To: mailing list scm@geronimo.apache.org Received: (qmail 12600 invoked by uid 99); 3 Apr 2009 12:08:17 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 03 Apr 2009 12:08:17 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 03 Apr 2009 12:08:11 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 475BF2388A36; Fri, 3 Apr 2009 12:07:49 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: scm@geronimo.apache.org From: rickmcguire@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090403120749.475BF2388A36@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 @@ *
  • '*' which matches any character including hierarchy delimiters
  • * * 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 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 ...]